diff options
Diffstat (limited to 'src/corelib/io')
88 files changed, 52988 insertions, 0 deletions
diff --git a/src/corelib/io/io.pri b/src/corelib/io/io.pri new file mode 100644 index 0000000000..f67600d750 --- /dev/null +++ b/src/corelib/io/io.pri @@ -0,0 +1,119 @@ +# Qt core io module + +HEADERS += \ + io/qabstractfileengine.h \ + io/qabstractfileengine_p.h \ + io/qbuffer.h \ + io/qdatastream.h \ + io/qdatastream_p.h \ + io/qdataurl_p.h \ + io/qdebug.h \ + io/qdir.h \ + io/qdir_p.h \ + io/qdiriterator.h \ + io/qfile.h \ + io/qfileinfo.h \ + io/qfileinfo_p.h \ + io/qiodevice.h \ + io/qiodevice_p.h \ + io/qnoncontiguousbytedevice_p.h \ + io/qprocess.h \ + io/qprocess_p.h \ + io/qtextstream.h \ + io/qtemporaryfile.h \ + io/qresource_p.h \ + io/qresource_iterator_p.h \ + io/qurl.h \ + io/qsettings.h \ + io/qsettings_p.h \ + io/qfsfileengine.h \ + io/qfsfileengine_p.h \ + io/qfsfileengine_iterator_p.h \ + io/qfilesystemwatcher.h \ + io/qfilesystemwatcher_p.h \ + io/qfilesystementry_p.h \ + io/qfilesystemengine_p.h \ + io/qfilesystemmetadata_p.h \ + io/qfilesystemiterator_p.h + +SOURCES += \ + io/qabstractfileengine.cpp \ + io/qbuffer.cpp \ + io/qdatastream.cpp \ + io/qdataurl.cpp \ + io/qdebug.cpp \ + io/qdir.cpp \ + io/qdiriterator.cpp \ + io/qfile.cpp \ + io/qfileinfo.cpp \ + io/qiodevice.cpp \ + io/qnoncontiguousbytedevice.cpp \ + io/qprocess.cpp \ + io/qtextstream.cpp \ + io/qtemporaryfile.cpp \ + io/qresource.cpp \ + io/qresource_iterator.cpp \ + io/qurl.cpp \ + io/qsettings.cpp \ + io/qfsfileengine.cpp \ + io/qfsfileengine_iterator.cpp \ + io/qfilesystemwatcher.cpp \ + io/qfilesystementry.cpp \ + io/qfilesystemengine.cpp + +win32 { + SOURCES += io/qsettings_win.cpp + SOURCES += io/qprocess_win.cpp + SOURCES += io/qfsfileengine_win.cpp + + SOURCES += io/qfilesystemwatcher_win.cpp + HEADERS += io/qfilesystemwatcher_win_p.h + HEADERS += io/qwindowspipewriter_p.h + SOURCES += io/qwindowspipewriter.cpp + SOURCES += io/qfilesystemengine_win.cpp + SOURCES += io/qfilesystemiterator_win.cpp +} else:unix { + SOURCES += io/qfsfileengine_unix.cpp + symbian { + SOURCES += io/qfilesystemengine_symbian.cpp + SOURCES += io/qprocess_symbian.cpp + SOURCES += io/qfilesystemiterator_symbian.cpp + } else { + SOURCES += io/qfilesystemengine_unix.cpp + SOURCES += io/qprocess_unix.cpp + SOURCES += io/qfilesystemiterator_unix.cpp + } + !nacl:macx-*: { + HEADERS += io/qfilesystemwatcher_fsevents_p.h + SOURCES += io/qfilesystemengine_mac.cpp + SOURCES += io/qsettings_mac.cpp io/qfilesystemwatcher_fsevents.cpp + } + + linux-*:!symbian { + SOURCES += \ + io/qfilesystemwatcher_inotify.cpp \ + io/qfilesystemwatcher_dnotify.cpp + + HEADERS += \ + io/qfilesystemwatcher_inotify_p.h \ + io/qfilesystemwatcher_dnotify_p.h + } + + !nacl { + freebsd-*|macx-*|darwin-*|openbsd-*:{ + SOURCES += io/qfilesystemwatcher_kqueue.cpp + HEADERS += io/qfilesystemwatcher_kqueue_p.h + } + } + + symbian { + SOURCES += io/qfilesystemwatcher_symbian.cpp + HEADERS += io/qfilesystemwatcher_symbian_p.h + INCLUDEPATH += $$MW_LAYER_SYSTEMINCLUDE + LIBS += -lplatformenv -lesock + } +} +integrity { + SOURCES += io/qfsfileengine_unix.cpp \ + io/qfsfileengine_iterator_unix.cpp +} diff --git a/src/corelib/io/qabstractfileengine.cpp b/src/corelib/io/qabstractfileengine.cpp new file mode 100644 index 0000000000..0a423c0879 --- /dev/null +++ b/src/corelib/io/qabstractfileengine.cpp @@ -0,0 +1,1233 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractfileengine.h" +#include "private/qabstractfileengine_p.h" +#ifdef QT_BUILD_CORE_LIB +#include "private/qresource_p.h" +#endif +#include "qdatetime.h" +#include "qreadwritelock.h" +#include "qvariant.h" +// built-in handlers +#include "qfsfileengine.h" +#include "qdiriterator.h" +#include "qstringbuilder.h" + +#include <QtCore/private/qfilesystementry_p.h> +#include <QtCore/private/qfilesystemmetadata_p.h> +#include <QtCore/private/qfilesystemengine_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractFileEngineHandler + \reentrant + + \brief The QAbstractFileEngineHandler class provides a way to register + custom file engines with your application. + + \ingroup io + \since 4.1 + + QAbstractFileEngineHandler is a factory for creating QAbstractFileEngine + objects (file engines), which are used internally by QFile, QFileInfo, and + QDir when working with files and directories. + + When you open a file, Qt chooses a suitable file engine by passing the + file name from QFile or QDir through an internal list of registered file + engine handlers. The first handler to recognize the file name is used to + create the engine. Qt provides internal file engines for working with + regular files and resources, but you can also register your own + QAbstractFileEngine subclasses. + + To install an application-specific file engine, you subclass + QAbstractFileEngineHandler and reimplement create(). When you instantiate + the handler (e.g. by creating an instance on the stack or on the heap), it + will automatically register with Qt. (The latest registered handler takes + precedence over existing handlers.) + + For example: + + \snippet doc/src/snippets/code/src_corelib_io_qabstractfileengine.cpp 0 + + When the handler is destroyed, it is automatically removed from Qt. + + The most common approach to registering a handler is to create an instance + as part of the start-up phase of your application. It is also possible to + limit the scope of the file engine handler to a particular area of + interest (e.g. a special file dialog that needs a custom file engine). By + creating the handler inside a local scope, you can precisely control the + area in which your engine will be applied without disturbing file + operations in other parts of your application. + + \sa QAbstractFileEngine, QAbstractFileEngine::create() +*/ + +static bool qt_file_engine_handlers_in_use = false; + +/* + All application-wide handlers are stored in this list. The mutex must be + acquired to ensure thread safety. + */ +Q_GLOBAL_STATIC_WITH_ARGS(QReadWriteLock, fileEngineHandlerMutex, (QReadWriteLock::Recursive)) +static bool qt_abstractfileenginehandlerlist_shutDown = false; +class QAbstractFileEngineHandlerList : public QList<QAbstractFileEngineHandler *> +{ +public: + ~QAbstractFileEngineHandlerList() + { + QWriteLocker locker(fileEngineHandlerMutex()); + qt_abstractfileenginehandlerlist_shutDown = true; + } +}; +Q_GLOBAL_STATIC(QAbstractFileEngineHandlerList, fileEngineHandlers) + +/*! + Constructs a file handler and registers it with Qt. Once created this + handler's create() function will be called (along with all the other + handlers) for any paths used. The most recently created handler that + recognizes the given path (i.e. that returns a QAbstractFileEngine) is + used for the new path. + + \sa create() + */ +QAbstractFileEngineHandler::QAbstractFileEngineHandler() +{ + QWriteLocker locker(fileEngineHandlerMutex()); + qt_file_engine_handlers_in_use = true; + fileEngineHandlers()->prepend(this); +} + +/*! + Destroys the file handler. This will automatically unregister the handler + from Qt. + */ +QAbstractFileEngineHandler::~QAbstractFileEngineHandler() +{ + QWriteLocker locker(fileEngineHandlerMutex()); + // Remove this handler from the handler list only if the list is valid. + if (!qt_abstractfileenginehandlerlist_shutDown) { + QAbstractFileEngineHandlerList *handlers = fileEngineHandlers(); + handlers->removeOne(this); + if (handlers->isEmpty()) + qt_file_engine_handlers_in_use = false; + } +} + +/* + \ìnternal + + Handles calls to custom file engine handlers. +*/ +QAbstractFileEngine *qt_custom_file_engine_handler_create(const QString &path) +{ + QAbstractFileEngine *engine = 0; + + if (qt_file_engine_handlers_in_use) { + QReadLocker locker(fileEngineHandlerMutex()); + + // check for registered handlers that can load the file + QAbstractFileEngineHandlerList *handlers = fileEngineHandlers(); + for (int i = 0; i < handlers->size(); i++) { + if ((engine = handlers->at(i)->create(path))) + break; + } + } + + return engine; +} + +/*! + \fn QAbstractFileEngine *QAbstractFileEngineHandler::create(const QString &fileName) const + + Creates a file engine for file \a fileName. Returns 0 if this + file handler cannot handle \a fileName. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qabstractfileengine.cpp 1 + + \sa QAbstractFileEngine::create() +*/ + +/*! + Creates and returns a QAbstractFileEngine suitable for processing \a + fileName. + + You should not need to call this function; use QFile, QFileInfo or + QDir directly instead. + + If you reimplemnt this function, it should only return file + engines that knows how to handle \a fileName; otherwise, it should + return 0. + + \sa QAbstractFileEngineHandler +*/ +QAbstractFileEngine *QAbstractFileEngine::create(const QString &fileName) +{ + QFileSystemEntry entry(fileName); + QFileSystemMetaData metaData; + QAbstractFileEngine *engine = QFileSystemEngine::resolveEntryAndCreateLegacyEngine(entry, metaData); + +#ifndef QT_NO_FSFILEENGINE + if (!engine) + // fall back to regular file engine + return new QFSFileEngine(entry.filePath()); +#endif + + return engine; +} + +/*! + \class QAbstractFileEngine + \reentrant + + \brief The QAbstractFileEngine class provides an abstraction for accessing + the filesystem. + + \ingroup io + \since 4.1 + + The QDir, QFile, and QFileInfo classes all make use of a + QAbstractFileEngine internally. If you create your own QAbstractFileEngine + subclass (and register it with Qt by creating a QAbstractFileEngineHandler + subclass), your file engine will be used when the path is one that your + file engine handles. + + A QAbstractFileEngine refers to one file or one directory. If the referent + is a file, the setFileName(), rename(), and remove() functions are + applicable. If the referent is a directory the mkdir(), rmdir(), and + entryList() functions are applicable. In all cases the caseSensitive(), + isRelativePath(), fileFlags(), ownerId(), owner(), and fileTime() + functions are applicable. + + A QAbstractFileEngine subclass can be created to do synchronous network I/O + based file system operations, local file system operations, or to operate + as a resource system to access file based resources. + + \sa QAbstractFileEngineHandler +*/ + +/*! + \enum QAbstractFileEngine::FileName + + These values are used to request a file name in a particular + format. + + \value DefaultName The same filename that was passed to the + QAbstractFileEngine. + \value BaseName The name of the file excluding the path. + \value PathName The path to the file excluding the base name. + \value AbsoluteName The absolute path to the file (including + the base name). + \value AbsolutePathName The absolute path to the file (excluding + the base name). + \value LinkName The full file name of the file that this file is a + link to. (This will be empty if this file is not a link.) + \value CanonicalName Often very similar to LinkName. Will return the true path to the file. + \value CanonicalPathName Same as CanonicalName, excluding the base name. + \value BundleName Returns the name of the bundle implies BundleType is set. + + \omitvalue NFileNames + + \sa fileName(), setFileName() +*/ + +/*! + \enum QAbstractFileEngine::FileFlag + + The permissions and types of a file, suitable for OR'ing together. + + \value ReadOwnerPerm The owner of the file has permission to read + it. + \value WriteOwnerPerm The owner of the file has permission to + write to it. + \value ExeOwnerPerm The owner of the file has permission to + execute it. + \value ReadUserPerm The current user has permission to read the + file. + \value WriteUserPerm The current user has permission to write to + the file. + \value ExeUserPerm The current user has permission to execute the + file. + \value ReadGroupPerm Members of the current user's group have + permission to read the file. + \value WriteGroupPerm Members of the current user's group have + permission to write to the file. + \value ExeGroupPerm Members of the current user's group have + permission to execute the file. + \value ReadOtherPerm All users have permission to read the file. + \value WriteOtherPerm All users have permission to write to the + file. + \value ExeOtherPerm All users have permission to execute the file. + + \value LinkType The file is a link to another file (or link) in + the file system (i.e. not a file or directory). + \value FileType The file is a regular file to the file system + (i.e. not a link or directory) + \value BundleType The file is a Mac OS X bundle implies DirectoryType + \value DirectoryType The file is a directory in the file system + (i.e. not a link or file). + + \value HiddenFlag The file is hidden. + \value ExistsFlag The file actually exists in the file system. + \value RootFlag The file or the file pointed to is the root of the filesystem. + \value LocalDiskFlag The file resides on the local disk and can be passed to standard file functions. + \value Refresh Passing this flag will force the file engine to refresh all flags. + + \omitvalue PermsMask + \omitvalue TypesMask + \omitvalue FlagsMask + \omitvalue FileInfoAll + + \sa fileFlags(), setFileName() +*/ + +/*! + \enum QAbstractFileEngine::FileTime + + These are used by the fileTime() function. + + \value CreationTime When the file was created. + \value ModificationTime When the file was most recently modified. + \value AccessTime When the file was most recently accessed (e.g. + read or written to). + + \sa setFileName() +*/ + +/*! + \enum QAbstractFileEngine::FileOwner + + \value OwnerUser The user who owns the file. + \value OwnerGroup The group who owns the file. + + \sa owner(), ownerId(), setFileName() +*/ + +/*! + Constructs a new QAbstractFileEngine that does not refer to any file or directory. + + \sa setFileName() + */ +QAbstractFileEngine::QAbstractFileEngine() : d_ptr(new QAbstractFileEnginePrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + \internal + + Constructs a QAbstractFileEngine. + */ +QAbstractFileEngine::QAbstractFileEngine(QAbstractFileEnginePrivate &dd) : d_ptr(&dd) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys the QAbstractFileEngine. + */ +QAbstractFileEngine::~QAbstractFileEngine() +{ +} + +/*! + \fn bool QAbstractFileEngine::open(QIODevice::OpenMode mode) + + Opens the file in the specified \a mode. Returns true if the file + was successfully opened; otherwise returns false. + + The \a mode is an OR combination of QIODevice::OpenMode and + QIODevice::HandlingMode values. +*/ +bool QAbstractFileEngine::open(QIODevice::OpenMode openMode) +{ + Q_UNUSED(openMode); + return false; +} + +/*! + Closes the file, returning true if successful; otherwise returns false. + + The default implementation always returns false. +*/ +bool QAbstractFileEngine::close() +{ + return false; +} + +/*! + Flushes the open file, returning true if successful; otherwise returns + false. + + The default implementation always returns false. +*/ +bool QAbstractFileEngine::flush() +{ + return false; +} + +/*! + Returns the size of the file. +*/ +qint64 QAbstractFileEngine::size() const +{ + return 0; +} + +/*! + Returns the current file position. + + This is the position of the data read/write head of the file. +*/ +qint64 QAbstractFileEngine::pos() const +{ + return 0; +} + +/*! + \fn bool QAbstractFileEngine::seek(qint64 offset) + + Sets the file position to the given \a offset. Returns true if + the position was successfully set; otherwise returns false. + + The offset is from the beginning of the file, unless the + file is sequential. + + \sa isSequential() +*/ +bool QAbstractFileEngine::seek(qint64 pos) +{ + Q_UNUSED(pos); + return false; +} + +/*! + Returns true if the file is a sequential access device; returns + false if the file is a direct access device. + + Operations involving size() and seek(int) are not valid on + sequential devices. +*/ +bool QAbstractFileEngine::isSequential() const +{ + return false; +} + +/*! + Requests that the file is deleted from the file system. If the + operation succeeds return true; otherwise return false. + + This virtual function must be reimplemented by all subclasses. + + \sa setFileName() rmdir() + */ +bool QAbstractFileEngine::remove() +{ + return false; +} + +/*! + Copies the contents of this file to a file with the name \a newName. + Returns true on success; otherwise, false is returned. +*/ +bool QAbstractFileEngine::copy(const QString &newName) +{ + Q_UNUSED(newName); + return false; +} + +/*! + Requests that the file be renamed to \a newName in the file + system. If the operation succeeds return true; otherwise return + false. + + This virtual function must be reimplemented by all subclasses. + + \sa setFileName() + */ +bool QAbstractFileEngine::rename(const QString &newName) +{ + Q_UNUSED(newName); + return false; +} + +/*! + Creates a link from the file currently specified by fileName() to + \a newName. What a link is depends on the underlying filesystem + (be it a shortcut on Windows or a symbolic link on Unix). Returns + true if successful; otherwise returns false. +*/ +bool QAbstractFileEngine::link(const QString &newName) +{ + Q_UNUSED(newName); + return false; +} + +/*! + Requests that the directory \a dirName be created. If + \a createParentDirectories is true, then any sub-directories in \a dirName + that don't exist must be created. If \a createParentDirectories is false then + any sub-directories in \a dirName must already exist for the function to + succeed. If the operation succeeds return true; otherwise return + false. + + This virtual function must be reimplemented by all subclasses. + + \sa setFileName() rmdir() isRelativePath() + */ +bool QAbstractFileEngine::mkdir(const QString &dirName, bool createParentDirectories) const +{ + Q_UNUSED(dirName); + Q_UNUSED(createParentDirectories); + return false; +} + +/*! + Requests that the directory \a dirName is deleted from the file + system. When \a recurseParentDirectories is true, then any empty + parent-directories in \a dirName must also be deleted. If + \a recurseParentDirectories is false, only the \a dirName leaf-node + should be deleted. In most file systems a directory cannot be deleted + using this function if it is non-empty. If the operation succeeds + return true; otherwise return false. + + This virtual function must be reimplemented by all subclasses. + + \sa setFileName() remove() mkdir() isRelativePath() + */ +bool QAbstractFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const +{ + Q_UNUSED(dirName); + Q_UNUSED(recurseParentDirectories); + return false; +} + +/*! + Requests that the file be set to size \a size. If \a size is larger + than the current file then it is filled with 0's, if smaller it is + simply truncated. If the operations succceeds return true; otherwise + return false; + + This virtual function must be reimplemented by all subclasses. + + \sa size() +*/ +bool QAbstractFileEngine::setSize(qint64 size) +{ + Q_UNUSED(size); + return false; +} + +/*! + Should return true if the underlying file system is case-sensitive; + otherwise return false. + + This virtual function must be reimplemented by all subclasses. + */ +bool QAbstractFileEngine::caseSensitive() const +{ + return false; +} + +/*! + Return true if the file referred to by this file engine has a + relative path; otherwise return false. + + This virtual function must be reimplemented by all subclasses. + + \sa setFileName() + */ +bool QAbstractFileEngine::isRelativePath() const +{ + return false; +} + +/*! + Requests that a list of all the files matching the \a filters + list based on the \a filterNames in the file engine's directory + are returned. + + Should return an empty list if the file engine refers to a file + rather than a directory, or if the directory is unreadable or does + not exist or if nothing matches the specifications. + + This virtual function must be reimplemented by all subclasses. + + \sa setFileName() + */ +QStringList QAbstractFileEngine::entryList(QDir::Filters filters, const QStringList &filterNames) const +{ + QStringList ret; + QDirIterator it(fileName(), filterNames, filters); + while (it.hasNext()) { + it.next(); + ret << it.fileName(); + } + return ret; +} + +/*! + This function should return the set of OR'd flags that are true + for the file engine's file, and that are in the \a type's OR'd + members. + + In your reimplementation you can use the \a type argument as an + optimization hint and only return the OR'd set of members that are + true and that match those in \a type; in other words you can + ignore any members not mentioned in \a type, thus avoiding some + potentially expensive lookups or system calls. + + This virtual function must be reimplemented by all subclasses. + + \sa setFileName() +*/ +QAbstractFileEngine::FileFlags QAbstractFileEngine::fileFlags(FileFlags type) const +{ + Q_UNUSED(type); + return 0; +} + +/*! + Requests that the file's permissions be set to \a perms. The argument + perms will be set to the OR-ed together combination of + QAbstractFileEngine::FileInfo, with only the QAbstractFileEngine::PermsMask being + honored. If the operations succceeds return true; otherwise return + false; + + This virtual function must be reimplemented by all subclasses. + + \sa size() +*/ +bool QAbstractFileEngine::setPermissions(uint perms) +{ + Q_UNUSED(perms); + return false; +} + +/*! + Return the file engine's current file name in the format + specified by \a file. + + If you don't handle some \c FileName possibilities, return the + file name set in setFileName() when an unhandled format is + requested. + + This virtual function must be reimplemented by all subclasses. + + \sa setFileName(), FileName + */ +QString QAbstractFileEngine::fileName(FileName file) const +{ + Q_UNUSED(file); + return QString(); +} + +/*! + If \a owner is \c OwnerUser return the ID of the user who owns + the file. If \a owner is \c OwnerGroup return the ID of the group + that own the file. If you can't determine the owner return -2. + + This virtual function must be reimplemented by all subclasses. + + \sa owner() setFileName(), FileOwner + */ +uint QAbstractFileEngine::ownerId(FileOwner owner) const +{ + Q_UNUSED(owner); + return 0; +} + +/*! + If \a owner is \c OwnerUser return the name of the user who owns + the file. If \a owner is \c OwnerGroup return the name of the group + that own the file. If you can't determine the owner return + QString(). + + This virtual function must be reimplemented by all subclasses. + + \sa ownerId() setFileName(), FileOwner + */ +QString QAbstractFileEngine::owner(FileOwner owner) const +{ + Q_UNUSED(owner); + return QString(); +} + +/*! + If \a time is \c CreationTime, return when the file was created. + If \a time is \c ModificationTime, return when the file was most + recently modified. If \a time is \c AccessTime, return when the + file was most recently accessed (e.g. read or written). + If the time cannot be determined return QDateTime() (an invalid + date time). + + This virtual function must be reimplemented by all subclasses. + + \sa setFileName(), QDateTime, QDateTime::isValid(), FileTime + */ +QDateTime QAbstractFileEngine::fileTime(FileTime time) const +{ + Q_UNUSED(time); + return QDateTime(); +} + +/*! + Sets the file engine's file name to \a file. This file name is the + file that the rest of the virtual functions will operate on. + + This virtual function must be reimplemented by all subclasses. + + \sa rename() + */ +void QAbstractFileEngine::setFileName(const QString &file) +{ + Q_UNUSED(file); +} + +/*! + Returns the native file handle for this file engine. This handle must be + used with care; its value and type are platform specific, and using it + will most likely lead to non-portable code. +*/ +int QAbstractFileEngine::handle() const +{ + return -1; +} + +/*! + \since 4.3 + + Returns true if the current position is at the end of the file; otherwise, + returns false. + + This function bases its behavior on calling extension() with + AtEndExtension. If the engine does not support this extension, false is + returned. + + \sa extension(), supportsExtension(), QFile::atEnd() +*/ +bool QAbstractFileEngine::atEnd() const +{ + return const_cast<QAbstractFileEngine *>(this)->extension(AtEndExtension); +} + +/*! + \since 4.4 + + Maps \a size bytes of the file into memory starting at \a offset. + Returns a pointer to the memory if successful; otherwise returns false + if, for example, an error occurs. + + This function bases its behavior on calling extension() with + MapExtensionOption. If the engine does not support this extension, 0 is + returned. + + \a flags is currently not used, but could be used in the future. + + \sa unmap(), supportsExtension() + */ + +uchar *QAbstractFileEngine::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags) +{ + MapExtensionOption option; + option.offset = offset; + option.size = size; + option.flags = flags; + MapExtensionReturn r; + if (!extension(MapExtension, &option, &r)) + return 0; + return r.address; +} + +/*! + \since 4.4 + + Unmaps the memory \a address. Returns true if the unmap succeeds; otherwise + returns false. + + This function bases its behavior on calling extension() with + UnMapExtensionOption. If the engine does not support this extension, false is + returned. + + \sa map(), supportsExtension() + */ +bool QAbstractFileEngine::unmap(uchar *address) +{ + UnMapExtensionOption options; + options.address = address; + return extension(UnMapExtension, &options); +} + +/*! + \since 4.3 + \class QAbstractFileEngineIterator + \brief The QAbstractFileEngineIterator class provides an iterator + interface for custom file engines. + + If all you want is to iterate over entries in a directory, see + QDirIterator instead. This class is only for custom file engine authors. + + QAbstractFileEngineIterator is a unidirectional single-use virtual + iterator that plugs into QDirIterator, providing transparent proxy + iteration for custom file engines. + + You can subclass QAbstractFileEngineIterator to provide an iterator when + writing your own file engine. To plug the iterator into your file system, + you simply return an instance of this subclass from a reimplementation of + QAbstractFileEngine::beginEntryList(). + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qabstractfileengine.cpp 2 + + QAbstractFileEngineIterator is associated with a path, name filters, and + entry filters. The path is the directory that the iterator lists entries + in. The name filters and entry filters are provided for file engines that + can optimize directory listing at the iterator level (e.g., network file + systems that need to minimize network traffic), but they can also be + ignored by the iterator subclass; QAbstractFileEngineIterator already + provides the required filtering logics in the matchesFilters() function. + You can call dirName() to get the directory name, nameFilters() to get a + stringlist of name filters, and filters() to get the entry filters. + + The pure virtual function hasNext() returns true if the current directory + has at least one more entry (i.e., the directory name is valid and + accessible, and we have not reached the end of the entry list), and false + otherwise. Reimplement next() to seek to the next entry. + + The pure virtual function currentFileName() returns the name of the + current entry without advancing the iterator. The currentFilePath() + function is provided for convenience; it returns the full path of the + current entry. + + Here is an example of how to implement an iterator that returns each of + three fixed entries in sequence. + + \snippet doc/src/snippets/code/src_corelib_io_qabstractfileengine.cpp 3 + + Note: QAbstractFileEngineIterator does not deal with QDir::IteratorFlags; + it simply returns entries for a single directory. + + \sa QDirIterator +*/ + +/*! + \enum QAbstractFileEngineIterator::EntryInfoType + \internal + + This enum describes the different types of information that can be + requested through the QAbstractFileEngineIterator::entryInfo() function. +*/ + +/*! + \typedef QAbstractFileEngine::Iterator + \since 4.3 + \relates QAbstractFileEngine + + Synonym for QAbstractFileEngineIterator. +*/ + +class QAbstractFileEngineIteratorPrivate +{ +public: + QString path; + QDir::Filters filters; + QStringList nameFilters; + QFileInfo fileInfo; +}; + +/*! + Constructs a QAbstractFileEngineIterator, using the entry filters \a + filters, and wildcard name filters \a nameFilters. +*/ +QAbstractFileEngineIterator::QAbstractFileEngineIterator(QDir::Filters filters, + const QStringList &nameFilters) + : d(new QAbstractFileEngineIteratorPrivate) +{ + d->nameFilters = nameFilters; + d->filters = filters; +} + +/*! + Destroys the QAbstractFileEngineIterator. + + \sa QDirIterator +*/ +QAbstractFileEngineIterator::~QAbstractFileEngineIterator() +{ +} + +/*! + Returns the path for this iterator. QDirIterator is responsible for + assigning this path; it cannot change during the iterator's lifetime. + + \sa nameFilters(), filters() +*/ +QString QAbstractFileEngineIterator::path() const +{ + return d->path; +} + +/*! + \internal + + Sets the iterator path to \a path. This function is called from within + QDirIterator. +*/ +void QAbstractFileEngineIterator::setPath(const QString &path) +{ + d->path = path; +} + +/*! + Returns the name filters for this iterator. + + \sa QDir::nameFilters(), filters(), path() +*/ +QStringList QAbstractFileEngineIterator::nameFilters() const +{ + return d->nameFilters; +} + +/*! + Returns the entry filters for this iterator. + + \sa QDir::filter(), nameFilters(), path() +*/ +QDir::Filters QAbstractFileEngineIterator::filters() const +{ + return d->filters; +} + +/*! + \fn QString QAbstractFileEngineIterator::currentFileName() const = 0 + + This pure virtual function returns the name of the current directory + entry, excluding the path. + + \sa currentFilePath() +*/ + +/*! + Returns the path to the current directory entry. It's the same as + prepending path() to the return value of currentFileName(). + + \sa currentFileName() +*/ +QString QAbstractFileEngineIterator::currentFilePath() const +{ + QString name = currentFileName(); + if (!name.isNull()) { + QString tmp = path(); + if (!tmp.isEmpty()) { + if (!tmp.endsWith(QLatin1Char('/'))) + tmp.append(QLatin1Char('/')); + name.prepend(tmp); + } + } + return name; +} + +/*! + The virtual function returns a QFileInfo for the current directory + entry. This function is provided for convenience. It can also be slightly + faster than creating a QFileInfo object yourself, as the object returned + by this function might contain cached information that QFileInfo otherwise + would have to access through the file engine. + + \sa currentFileName() +*/ +QFileInfo QAbstractFileEngineIterator::currentFileInfo() const +{ + QString path = currentFilePath(); + if (d->fileInfo.filePath() != path) + d->fileInfo.setFile(path); + + // return a shallow copy + return d->fileInfo; +} + +/*! + \internal + + Returns the entry info \a type for this iterator's current directory entry + as a QVariant. If \a type is undefined for this entry, a null QVariant is + returned. + + \sa QAbstractFileEngine::beginEntryList(), QDir::beginEntryList() +*/ +QVariant QAbstractFileEngineIterator::entryInfo(EntryInfoType type) const +{ + Q_UNUSED(type) + return QVariant(); +} + +/*! + \fn virtual QString QAbstractFileEngineIterator::next() = 0 + + This pure virtual function advances the iterator to the next directory + entry, and returns the file path to the current entry. + + This function can optionally make use of nameFilters() and filters() to + optimize its performance. + + Reimplement this function in a subclass to advance the iterator. + + \sa QDirIterator::next() +*/ + +/*! + \fn virtual bool QAbstractFileEngineIterator::hasNext() const = 0 + + This pure virtual function returns true if there is at least one more + entry in the current directory (i.e., the iterator path is valid and + accessible, and the iterator has not reached the end of the entry list). + + \sa QDirIterator::hasNext() +*/ + +/*! + Returns an instance of a QAbstractFileEngineIterator using \a filters for + entry filtering and \a filterNames for name filtering. This function is + called by QDirIterator to initiate directory iteration. + + QDirIterator takes ownership of the returned instance, and deletes it when + it's done. + + \sa QDirIterator +*/ +QAbstractFileEngine::Iterator *QAbstractFileEngine::beginEntryList(QDir::Filters filters, const QStringList &filterNames) +{ + Q_UNUSED(filters); + Q_UNUSED(filterNames); + return 0; +} + +/*! + \internal +*/ +QAbstractFileEngine::Iterator *QAbstractFileEngine::endEntryList() +{ + return 0; +} + +/*! + Reads a number of characters from the file into \a data. At most + \a maxlen characters will be read. + + Returns -1 if a fatal error occurs, or 0 if there are no bytes to + read. +*/ +qint64 QAbstractFileEngine::read(char *data, qint64 maxlen) +{ + Q_UNUSED(data); + Q_UNUSED(maxlen); + return -1; +} + +/*! + Writes \a len bytes from \a data to the file. Returns the number + of characters written on success; otherwise returns -1. +*/ +qint64 QAbstractFileEngine::write(const char *data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + return -1; +} + +/*! + This function reads one line, terminated by a '\n' character, from the + file info \a data. At most \a maxlen characters will be read. The + end-of-line character is included. +*/ +qint64 QAbstractFileEngine::readLine(char *data, qint64 maxlen) +{ + qint64 readSoFar = 0; + while (readSoFar < maxlen) { + char c; + qint64 readResult = read(&c, 1); + if (readResult <= 0) + return (readSoFar > 0) ? readSoFar : -1; + ++readSoFar; + *data++ = c; + if (c == '\n') + return readSoFar; + } + return readSoFar; +} + +/*! + \enum QAbstractFileEngine::Extension + \since 4.3 + + This enum describes the types of extensions that the file engine can + support. Before using these extensions, you must verify that the extension + is supported (i.e., call supportsExtension()). + + \value AtEndExtension Whether the current file position is at the end of + the file or not. This extension allows file engines that implement local + buffering to report end-of-file status without having to check the size of + the file. It is also useful for sequential files, where the size of the + file cannot be used to determine whether or not you have reached the end. + This extension returns true if the file is at the end; otherwise it returns + false. The input and output arguments to extension() are ignored. + + \value FastReadLineExtension Whether the file engine provides a + fast implementation for readLine() or not. If readLine() remains + unimplemented in the file engine, QAbstractFileEngine will provide + an implementation based on calling read() repeatedly. If + supportsExtension() returns false for this extension, however, + QIODevice can provide a faster implementation by making use of its + internal buffer. For engines that already provide a fast readLine() + implementation, returning false for this extension can avoid + unnnecessary double-buffering in QIODevice. + + \value MapExtension Whether the file engine provides the ability to map + a file to memory. + + \value UnMapExtension Whether the file engine provides the ability to + unmap memory that was previously mapped. +*/ + +/*! + \class QAbstractFileEngine::ExtensionOption + \since 4.3 + \brief provides an extended input argument to QAbstractFileEngine's + extension support. + + \sa QAbstractFileEngine::extension() +*/ + +/*! + \class QAbstractFileEngine::ExtensionReturn + \since 4.3 + \brief provides an extended output argument to QAbstractFileEngine's + extension support. + + \sa QAbstractFileEngine::extension() +*/ + +/*! + \since 4.3 + + This virtual function can be reimplemented in a QAbstractFileEngine + subclass to provide support for extensions. The \a option argument is + provided as input to the extension, and this function can store output + results in \a output. + + The behavior of this function is determined by \a extension; see the + Extension documentation for details. + + You can call supportsExtension() to check if an extension is supported by + the file engine. + + By default, no extensions are supported, and this function returns false. + + \sa supportsExtension(), Extension +*/ +bool QAbstractFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) +{ + Q_UNUSED(extension); + Q_UNUSED(option); + Q_UNUSED(output); + return false; +} + +/*! + \since 4.3 + + This virtual function returns true if the file engine supports \a + extension; otherwise, false is returned. By default, no extensions are + supported. + + \sa extension() +*/ +bool QAbstractFileEngine::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + Returns the QFile::FileError that resulted from the last failed + operation. If QFile::UnspecifiedError is returned, QFile will + use its own idea of the error status. + + \sa QFile::FileError, errorString() + */ +QFile::FileError QAbstractFileEngine::error() const +{ + Q_D(const QAbstractFileEngine); + return d->fileError; +} + +/*! + Returns the human-readable message appropriate to the current error + reported by error(). If no suitable string is available, an + empty string is returned. + + \sa error() + */ +QString QAbstractFileEngine::errorString() const +{ + Q_D(const QAbstractFileEngine); + return d->errorString; +} + +/*! + Sets the error type to \a error, and the error string to \a errorString. + Call this function to set the error values returned by the higher-level + classes. + + \sa QFile::error(), QIODevice::errorString(), QIODevice::setErrorString() +*/ +void QAbstractFileEngine::setError(QFile::FileError error, const QString &errorString) +{ + Q_D(QAbstractFileEngine); + d->fileError = error; + d->errorString = errorString; +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qabstractfileengine.h b/src/corelib/io/qabstractfileengine.h new file mode 100644 index 0000000000..91d3a5ef7e --- /dev/null +++ b/src/corelib/io/qabstractfileengine.h @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTFILEENGINE_H +#define QABSTRACTFILEENGINE_H + +#include <QtCore/qdir.h> + +#ifdef open +#error qabstractfileengine.h must be included before any header file that defines open +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QFileExtension; +class QFileExtensionResult; +class QVariant; +class QAbstractFileEngineIterator; +class QAbstractFileEnginePrivate; + +class Q_CORE_EXPORT QAbstractFileEngine +{ +public: + enum FileFlag { + //perms (overlaps the QFile::Permission) + ReadOwnerPerm = 0x4000, WriteOwnerPerm = 0x2000, ExeOwnerPerm = 0x1000, + ReadUserPerm = 0x0400, WriteUserPerm = 0x0200, ExeUserPerm = 0x0100, + ReadGroupPerm = 0x0040, WriteGroupPerm = 0x0020, ExeGroupPerm = 0x0010, + ReadOtherPerm = 0x0004, WriteOtherPerm = 0x0002, ExeOtherPerm = 0x0001, + + //types + LinkType = 0x10000, + FileType = 0x20000, + DirectoryType = 0x40000, + BundleType = 0x80000, + + //flags + HiddenFlag = 0x0100000, + LocalDiskFlag = 0x0200000, + ExistsFlag = 0x0400000, + RootFlag = 0x0800000, + Refresh = 0x1000000, + + //masks + PermsMask = 0x0000FFFF, + TypesMask = 0x000F0000, + FlagsMask = 0x0FF00000, + FileInfoAll = FlagsMask | PermsMask | TypesMask + }; + Q_DECLARE_FLAGS(FileFlags, FileFlag) + + enum FileName { + DefaultName, + BaseName, + PathName, + AbsoluteName, + AbsolutePathName, + LinkName, + CanonicalName, + CanonicalPathName, + BundleName, + NFileNames = 9 + }; + enum FileOwner { + OwnerUser, + OwnerGroup + }; + enum FileTime { + CreationTime, + ModificationTime, + AccessTime + }; + + virtual ~QAbstractFileEngine(); + + virtual bool open(QIODevice::OpenMode openMode); + virtual bool close(); + virtual bool flush(); + virtual qint64 size() const; + virtual qint64 pos() const; + virtual bool seek(qint64 pos); + virtual bool isSequential() const; + virtual bool remove(); + virtual bool copy(const QString &newName); + virtual bool rename(const QString &newName); + virtual bool link(const QString &newName); + virtual bool mkdir(const QString &dirName, bool createParentDirectories) const; + virtual bool rmdir(const QString &dirName, bool recurseParentDirectories) const; + virtual bool setSize(qint64 size); + virtual bool caseSensitive() const; + virtual bool isRelativePath() const; + virtual QStringList entryList(QDir::Filters filters, const QStringList &filterNames) const; + virtual FileFlags fileFlags(FileFlags type=FileInfoAll) const; + virtual bool setPermissions(uint perms); + virtual QString fileName(FileName file=DefaultName) const; + virtual uint ownerId(FileOwner) const; + virtual QString owner(FileOwner) const; + virtual QDateTime fileTime(FileTime time) const; + virtual void setFileName(const QString &file); + virtual int handle() const; + bool atEnd() const; + uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags); + bool unmap(uchar *ptr); + + typedef QAbstractFileEngineIterator Iterator; + virtual Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames); + virtual Iterator *endEntryList(); + + virtual qint64 read(char *data, qint64 maxlen); + virtual qint64 readLine(char *data, qint64 maxlen); + virtual qint64 write(const char *data, qint64 len); + + QFile::FileError error() const; + QString errorString() const; + + enum Extension { + AtEndExtension, + FastReadLineExtension, + MapExtension, + UnMapExtension + }; + class ExtensionOption + {}; + class ExtensionReturn + {}; + + class MapExtensionOption : public ExtensionOption { + public: + qint64 offset; + qint64 size; + QFile::MemoryMapFlags flags; + }; + class MapExtensionReturn : public ExtensionReturn { + public: + uchar *address; + }; + + class UnMapExtensionOption : public ExtensionOption { + public: + uchar *address; + }; + + virtual bool extension(Extension extension, const ExtensionOption *option = 0, ExtensionReturn *output = 0); + virtual bool supportsExtension(Extension extension) const; + + // Factory + static QAbstractFileEngine *create(const QString &fileName); + +protected: + void setError(QFile::FileError error, const QString &str); + + QAbstractFileEngine(); + QAbstractFileEngine(QAbstractFileEnginePrivate &); + + QScopedPointer<QAbstractFileEnginePrivate> d_ptr; +private: + Q_DECLARE_PRIVATE(QAbstractFileEngine) + Q_DISABLE_COPY(QAbstractFileEngine) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractFileEngine::FileFlags) + +class Q_CORE_EXPORT QAbstractFileEngineHandler +{ +public: + QAbstractFileEngineHandler(); + virtual ~QAbstractFileEngineHandler(); + virtual QAbstractFileEngine *create(const QString &fileName) const = 0; +}; + +class QAbstractFileEngineIteratorPrivate; +class Q_CORE_EXPORT QAbstractFileEngineIterator +{ +public: + QAbstractFileEngineIterator(QDir::Filters filters, const QStringList &nameFilters); + virtual ~QAbstractFileEngineIterator(); + + virtual QString next() = 0; + virtual bool hasNext() const = 0; + + QString path() const; + QStringList nameFilters() const; + QDir::Filters filters() const; + + virtual QString currentFileName() const = 0; + virtual QFileInfo currentFileInfo() const; + QString currentFilePath() const; + +protected: + enum EntryInfoType { + }; + virtual QVariant entryInfo(EntryInfoType type) const; + +private: + Q_DISABLE_COPY(QAbstractFileEngineIterator) + friend class QDirIterator; + friend class QDirIteratorPrivate; + void setPath(const QString &path); + QScopedPointer<QAbstractFileEngineIteratorPrivate> d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTFILEENGINE_H diff --git a/src/corelib/io/qabstractfileengine_p.h b/src/corelib/io/qabstractfileengine_p.h new file mode 100644 index 0000000000..d64eaa56c0 --- /dev/null +++ b/src/corelib/io/qabstractfileengine_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTFILEENGINE_P_H +#define QABSTRACTFILEENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qabstractfileengine.h" +#include "QtCore/qfile.h" + +QT_BEGIN_NAMESPACE + +class QAbstractFileEnginePrivate +{ +public: + inline QAbstractFileEnginePrivate() + : fileError(QFile::UnspecifiedError) + { + } + inline virtual ~QAbstractFileEnginePrivate() { } + + QFile::FileError fileError; + QString errorString; + + QAbstractFileEngine *q_ptr; + Q_DECLARE_PUBLIC(QAbstractFileEngine) +}; + +QAbstractFileEngine *qt_custom_file_engine_handler_create(const QString &path); + +QT_END_NAMESPACE + +#endif // QABSTRACTFILEENGINE_P_H diff --git a/src/corelib/io/qbuffer.cpp b/src/corelib/io/qbuffer.cpp new file mode 100644 index 0000000000..c5c21659b9 --- /dev/null +++ b/src/corelib/io/qbuffer.cpp @@ -0,0 +1,493 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbuffer.h" +#include "private/qiodevice_p.h" + +QT_BEGIN_NAMESPACE + +/** QBufferPrivate **/ +class QBufferPrivate : public QIODevicePrivate +{ + Q_DECLARE_PUBLIC(QBuffer) + +public: + QBufferPrivate() + : buf(0) +#ifndef QT_NO_QOBJECT + , writtenSinceLastEmit(0), signalConnectionCount(0), signalsEmitted(false) +#endif + { } + ~QBufferPrivate() { } + + QByteArray *buf; + QByteArray defaultBuf; + int ioIndex; + + virtual qint64 peek(char *data, qint64 maxSize); + virtual QByteArray peek(qint64 maxSize); + +#ifndef QT_NO_QOBJECT + // private slots + void _q_emitSignals(); + + qint64 writtenSinceLastEmit; + int signalConnectionCount; + bool signalsEmitted; +#endif +}; + +#ifndef QT_NO_QOBJECT +void QBufferPrivate::_q_emitSignals() +{ + Q_Q(QBuffer); + emit q->bytesWritten(writtenSinceLastEmit); + writtenSinceLastEmit = 0; + emit q->readyRead(); + signalsEmitted = false; +} +#endif + +qint64 QBufferPrivate::peek(char *data, qint64 maxSize) +{ + qint64 readBytes = qMin(maxSize, static_cast<qint64>(buf->size()) - pos); + memcpy(data, buf->constData() + pos, readBytes); + return readBytes; +} + +QByteArray QBufferPrivate::peek(qint64 maxSize) +{ + qint64 readBytes = qMin(maxSize, static_cast<qint64>(buf->size()) - pos); + if (pos == 0 && maxSize >= buf->size()) + return *buf; + return QByteArray(buf->constData() + pos, readBytes); +} + +/*! + \class QBuffer + \reentrant + \brief The QBuffer class provides a QIODevice interface for a QByteArray. + + \ingroup io + + QBuffer allows you to access a QByteArray using the QIODevice + interface. The QByteArray is treated just as a standard random-accessed + file. Example: + + \snippet doc/src/snippets/buffer/buffer.cpp 0 + + By default, an internal QByteArray buffer is created for you when + you create a QBuffer. You can access this buffer directly by + calling buffer(). You can also use QBuffer with an existing + QByteArray by calling setBuffer(), or by passing your array to + QBuffer's constructor. + + Call open() to open the buffer. Then call write() or + putChar() to write to the buffer, and read(), readLine(), + readAll(), or getChar() to read from it. size() returns the + current size of the buffer, and you can seek to arbitrary + positions in the buffer by calling seek(). When you are done with + accessing the buffer, call close(). + + The following code snippet shows how to write data to a + QByteArray using QDataStream and QBuffer: + + \snippet doc/src/snippets/buffer/buffer.cpp 1 + + Effectively, we convert the application's QPalette into a byte + array. Here's how to read the data from the QByteArray: + + \snippet doc/src/snippets/buffer/buffer.cpp 2 + + QTextStream and QDataStream also provide convenience constructors + that take a QByteArray and that create a QBuffer behind the + scenes. + + QBuffer emits readyRead() when new data has arrived in the + buffer. By connecting to this signal, you can use QBuffer to + store temporary data before processing it. For example, you can + pass the buffer to QFtp when downloading a file from an FTP + server. Whenever a new payload of data has been downloaded, + readyRead() is emitted, and you can process the data that just + arrived. QBuffer also emits bytesWritten() every time new data + has been written to the buffer. + + \sa QFile, QDataStream, QTextStream, QByteArray +*/ + +#ifdef QT_NO_QOBJECT +QBuffer::QBuffer() + : QIODevice(*new QBufferPrivate) +{ + Q_D(QBuffer); + d->buf = &d->defaultBuf; + d->ioIndex = 0; +} +QBuffer::QBuffer(QByteArray *buf) + : QIODevice(*new QBufferPrivate) +{ + Q_D(QBuffer); + d->buf = buf ? buf : &d->defaultBuf; + d->ioIndex = 0; + d->defaultBuf.clear(); +} +#else +/*! + Constructs an empty buffer with the given \a parent. You can call + setData() to fill the buffer with data, or you can open it in + write mode and use write(). + + \sa open() +*/ +QBuffer::QBuffer(QObject *parent) + : QIODevice(*new QBufferPrivate, parent) +{ + Q_D(QBuffer); + d->buf = &d->defaultBuf; + d->ioIndex = 0; +} + +/*! + Constructs a QBuffer that uses the QByteArray pointed to by \a + byteArray as its internal buffer, and with the given \a parent. + The caller is responsible for ensuring that \a byteArray remains + valid until the QBuffer is destroyed, or until setBuffer() is + called to change the buffer. QBuffer doesn't take ownership of + the QByteArray. + + If you open the buffer in write-only mode or read-write mode and + write something into the QBuffer, \a byteArray will be modified. + + Example: + + \snippet doc/src/snippets/buffer/buffer.cpp 3 + + \sa open(), setBuffer(), setData() +*/ +QBuffer::QBuffer(QByteArray *byteArray, QObject *parent) + : QIODevice(*new QBufferPrivate, parent) +{ + Q_D(QBuffer); + d->buf = byteArray ? byteArray : &d->defaultBuf; + d->defaultBuf.clear(); + d->ioIndex = 0; +} +#endif + +/*! + Destroys the buffer. +*/ + +QBuffer::~QBuffer() +{ +} + +/*! + Makes QBuffer uses the QByteArray pointed to by \a + byteArray as its internal buffer. The caller is responsible for + ensuring that \a byteArray remains valid until the QBuffer is + destroyed, or until setBuffer() is called to change the buffer. + QBuffer doesn't take ownership of the QByteArray. + + Does nothing if isOpen() is true. + + If you open the buffer in write-only mode or read-write mode and + write something into the QBuffer, \a byteArray will be modified. + + Example: + + \snippet doc/src/snippets/buffer/buffer.cpp 4 + + If \a byteArray is 0, the buffer creates its own internal + QByteArray to work on. This byte array is initially empty. + + \sa buffer(), setData(), open() +*/ + +void QBuffer::setBuffer(QByteArray *byteArray) +{ + Q_D(QBuffer); + if (isOpen()) { + qWarning("QBuffer::setBuffer: Buffer is open"); + return; + } + if (byteArray) { + d->buf = byteArray; + } else { + d->buf = &d->defaultBuf; + } + d->defaultBuf.clear(); + d->ioIndex = 0; +} + +/*! + Returns a reference to the QBuffer's internal buffer. You can use + it to modify the QByteArray behind the QBuffer's back. + + \sa setBuffer(), data() +*/ + +QByteArray &QBuffer::buffer() +{ + Q_D(QBuffer); + return *d->buf; +} + +/*! + \overload + + This is the same as data(). +*/ + +const QByteArray &QBuffer::buffer() const +{ + Q_D(const QBuffer); + return *d->buf; +} + + +/*! + Returns the data contained in the buffer. + + This is the same as buffer(). + + \sa setData(), setBuffer() +*/ + +const QByteArray &QBuffer::data() const +{ + Q_D(const QBuffer); + return *d->buf; +} + +/*! + Sets the contents of the internal buffer to be \a data. This is + the same as assigning \a data to buffer(). + + Does nothing if isOpen() is true. + + \sa setBuffer() +*/ +void QBuffer::setData(const QByteArray &data) +{ + Q_D(QBuffer); + if (isOpen()) { + qWarning("QBuffer::setData: Buffer is open"); + return; + } + *d->buf = data; + d->ioIndex = 0; +} + +/*! + \fn void QBuffer::setData(const char *data, int size) + + \overload + + Sets the contents of the internal buffer to be the first \a size + bytes of \a data. +*/ + +/*! + \reimp +*/ +bool QBuffer::open(OpenMode flags) +{ + Q_D(QBuffer); + + if ((flags & Append) == Append) + flags |= WriteOnly; + setOpenMode(flags); + if (!(isReadable() || isWritable())) { + qWarning("QFile::open: File access not specified"); + return false; + } + + if ((flags & QIODevice::Truncate) == QIODevice::Truncate) { + d->buf->resize(0); + } + if ((flags & QIODevice::Append) == QIODevice::Append) // append to end of buffer + seek(d->buf->size()); + else + seek(0); + + return true; +} + +/*! + \reimp +*/ +void QBuffer::close() +{ + QIODevice::close(); +} + +/*! + \reimp +*/ +qint64 QBuffer::pos() const +{ + return QIODevice::pos(); +} + +/*! + \reimp +*/ +qint64 QBuffer::size() const +{ + Q_D(const QBuffer); + return qint64(d->buf->size()); +} + +/*! + \reimp +*/ +bool QBuffer::seek(qint64 pos) +{ + Q_D(QBuffer); + if (pos > d->buf->size() && isWritable()) { + if (seek(d->buf->size())) { + const qint64 gapSize = pos - d->buf->size(); + if (write(QByteArray(gapSize, 0)) != gapSize) { + qWarning("QBuffer::seek: Unable to fill gap"); + return false; + } + } else { + return false; + } + } else if (pos > d->buf->size() || pos < 0) { + qWarning("QBuffer::seek: Invalid pos: %d", int(pos)); + return false; + } + d->ioIndex = int(pos); + return QIODevice::seek(pos); +} + +/*! + \reimp +*/ +bool QBuffer::atEnd() const +{ + return QIODevice::atEnd(); +} + +/*! + \reimp +*/ +bool QBuffer::canReadLine() const +{ + Q_D(const QBuffer); + if (!isOpen()) + return false; + + return d->buf->indexOf('\n', int(pos())) != -1 || QIODevice::canReadLine(); +} + +/*! + \reimp +*/ +qint64 QBuffer::readData(char *data, qint64 len) +{ + Q_D(QBuffer); + if ((len = qMin(len, qint64(d->buf->size()) - d->ioIndex)) <= 0) + return qint64(0); + memcpy(data, d->buf->constData() + d->ioIndex, len); + d->ioIndex += int(len); + return len; +} + +/*! + \reimp +*/ +qint64 QBuffer::writeData(const char *data, qint64 len) +{ + Q_D(QBuffer); + int extraBytes = d->ioIndex + len - d->buf->size(); + if (extraBytes > 0) { // overflow + int newSize = d->buf->size() + extraBytes; + d->buf->resize(newSize); + if (d->buf->size() != newSize) { // could not resize + qWarning("QBuffer::writeData: Memory allocation error"); + return -1; + } + } + + memcpy(d->buf->data() + d->ioIndex, (uchar *)data, int(len)); + d->ioIndex += int(len); + +#ifndef QT_NO_QOBJECT + d->writtenSinceLastEmit += len; + if (d->signalConnectionCount && !d->signalsEmitted && !signalsBlocked()) { + d->signalsEmitted = true; + QMetaObject::invokeMethod(this, "_q_emitSignals", Qt::QueuedConnection); + } +#endif + return len; +} + +#ifndef QT_NO_QOBJECT +/*! + \reimp + \internal +*/ +void QBuffer::connectNotify(const char *signal) +{ + if (strcmp(signal + 1, "readyRead()") == 0 || strcmp(signal + 1, "bytesWritten(qint64)") == 0) + d_func()->signalConnectionCount++; +} + +/*! + \reimp + \internal +*/ +void QBuffer::disconnectNotify(const char *signal) +{ + if (!signal || strcmp(signal + 1, "readyRead()") == 0 || strcmp(signal + 1, "bytesWritten(qint64)") == 0) + d_func()->signalConnectionCount--; +} +#endif + +QT_END_NAMESPACE + +#ifndef QT_NO_QOBJECT +# include "moc_qbuffer.cpp" +#endif + diff --git a/src/corelib/io/qbuffer.h b/src/corelib/io/qbuffer.h new file mode 100644 index 0000000000..a6cb87bc3f --- /dev/null +++ b/src/corelib/io/qbuffer.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBUFFER_H +#define QBUFFER_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qbytearray.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QObject; +class QBufferPrivate; + +class Q_CORE_EXPORT QBuffer : public QIODevice +{ +#ifndef QT_NO_QOBJECT + Q_OBJECT +#endif + +public: +#ifndef QT_NO_QOBJECT + explicit QBuffer(QObject *parent = 0); + QBuffer(QByteArray *buf, QObject *parent = 0); +#else + QBuffer(); + explicit QBuffer(QByteArray *buf); +#endif + ~QBuffer(); + + QByteArray &buffer(); + const QByteArray &buffer() const; + void setBuffer(QByteArray *a); + + void setData(const QByteArray &data); + inline void setData(const char *data, int len); + const QByteArray &data() const; + + bool open(OpenMode openMode); + + void close(); + qint64 size() const; + qint64 pos() const; + bool seek(qint64 off); + bool atEnd() const; + bool canReadLine() const; + +protected: +#ifndef QT_NO_QOBJECT + void connectNotify(const char*); + void disconnectNotify(const char*); +#endif + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + +private: + Q_DECLARE_PRIVATE(QBuffer) + Q_DISABLE_COPY(QBuffer) + + Q_PRIVATE_SLOT(d_func(), void _q_emitSignals()) +}; + +inline void QBuffer::setData(const char *adata, int alen) +{ setData(QByteArray(adata, alen)); } + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QBUFFER_H diff --git a/src/corelib/io/qdatastream.cpp b/src/corelib/io/qdatastream.cpp new file mode 100644 index 0000000000..0361d180b2 --- /dev/null +++ b/src/corelib/io/qdatastream.cpp @@ -0,0 +1,1325 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdatastream.h" +#include "qdatastream_p.h" + +#if !defined(QT_NO_DATASTREAM) || defined(QT_BOOTSTRAPPED) +#include "qbuffer.h" +#include "qstring.h" +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include "qendian.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDataStream + \reentrant + \brief The QDataStream class provides serialization of binary data + to a QIODevice. + + \ingroup io + + + A data stream is a binary stream of encoded information which is + 100% independent of the host computer's operating system, CPU or + byte order. For example, a data stream that is written by a PC + under Windows can be read by a Sun SPARC running Solaris. + + You can also use a data stream to read/write \l{raw}{raw + unencoded binary data}. If you want a "parsing" input stream, see + QTextStream. + + The QDataStream class implements the serialization of C++'s basic + data types, like \c char, \c short, \c int, \c{char *}, etc. + Serialization of more complex data is accomplished by breaking up + the data into primitive units. + + A data stream cooperates closely with a QIODevice. A QIODevice + represents an input/output medium one can read data from and write + data to. The QFile class is an example of an I/O device. + + Example (write binary data to a stream): + + \snippet doc/src/snippets/code/src_corelib_io_qdatastream.cpp 0 + + Example (read binary data from a stream): + + \snippet doc/src/snippets/code/src_corelib_io_qdatastream.cpp 1 + + Each item written to the stream is written in a predefined binary + format that varies depending on the item's type. Supported Qt + types include QBrush, QColor, QDateTime, QFont, QPixmap, QString, + QVariant and many others. For the complete list of all Qt types + supporting data streaming see \l{Serializing Qt Data Types}. + + For integers it is best to always cast to a Qt integer type for + writing, and to read back into the same Qt integer type. This + ensures that you get integers of the size you want and insulates + you from compiler and platform differences. + + To take one example, a \c{char *} string is written as a 32-bit + integer equal to the length of the string including the '\\0' byte, + followed by all the characters of the string including the + '\\0' byte. When reading a \c{char *} string, 4 bytes are read to + create the 32-bit length value, then that many characters for the + \c {char *} string including the '\\0' terminator are read. + + The initial I/O device is usually set in the constructor, but can be + changed with setDevice(). If you've reached the end of the data + (or if there is no I/O device set) atEnd() will return true. + + \section1 Versioning + + QDataStream's binary format has evolved since Qt 1.0, and is + likely to continue evolving to reflect changes done in Qt. When + inputting or outputting complex types, it's very important to + make sure that the same version of the stream (version()) is used + for reading and writing. If you need both forward and backward + compatibility, you can hardcode the version number in the + application: + + \snippet doc/src/snippets/code/src_corelib_io_qdatastream.cpp 2 + + If you are producing a new binary data format, such as a file + format for documents created by your application, you could use a + QDataStream to write the data in a portable format. Typically, you + would write a brief header containing a magic string and a version + number to give yourself room for future expansion. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qdatastream.cpp 3 + + Then read it in with: + + \snippet doc/src/snippets/code/src_corelib_io_qdatastream.cpp 4 + + You can select which byte order to use when serializing data. The + default setting is big endian (MSB first). Changing it to little + endian breaks the portability (unless the reader also changes to + little endian). We recommend keeping this setting unless you have + special requirements. + + \target raw + \section1 Reading and writing raw binary data + + You may wish to read/write your own raw binary data to/from the + data stream directly. Data may be read from the stream into a + preallocated \c{char *} using readRawData(). Similarly data can be + written to the stream using writeRawData(). Note that any + encoding/decoding of the data must be done by you. + + A similar pair of functions is readBytes() and writeBytes(). These + differ from their \e raw counterparts as follows: readBytes() + reads a quint32 which is taken to be the length of the data to be + read, then that number of bytes is read into the preallocated + \c{char *}; writeBytes() writes a quint32 containing the length of the + data, followed by the data. Note that any encoding/decoding of + the data (apart from the length quint32) must be done by you. + + \section1 Reading and writing Qt collection classes + + The Qt container classes can also be serialized to a QDataStream. + These include QList, QLinkedList, QVector, QSet, QHash, and QMap. + The stream operators are declared as non-members of the classes. + + \target Serializing Qt Classes + \section1 Reading and writing other Qt classes. + + In addition to the overloaded stream operators documented here, + any Qt classes that you might want to serialize to a QDataStream + will have appropriate stream operators declared as non-member of + the class: + + \code + QDataStream &operator<<(QDataStream &, const QXxx &); + QDataStream &operator>>(QDataStream &, QXxx &); + \endcode + + For example, here are the stream operators declared as non-members + of the QImage class: + + \code + QDataStream & operator<< (QDataStream& stream, const QImage& image); + QDataStream & operator>> (QDataStream& stream, QImage& image); + \endcode + + To see if your favorite Qt class has similar stream operators + defined, check the \bold {Related Non-Members} section of the + class's documentation page. + + \sa QTextStream QVariant +*/ + +/*! + \enum QDataStream::ByteOrder + + The byte order used for reading/writing the data. + + \value BigEndian Most significant byte first (the default) + \value LittleEndian Least significant byte first +*/ + +/*! + \enum QDataStream::FloatingPointPrecision + + The precision of floating point numbers used for reading/writing the data. This will only have + an effect if the version of the data stream is Qt_4_6 or higher. + + \warning The floating point precision must be set to the same value on the object that writes + and the object that reads the data stream. + + \value SinglePrecision All floating point numbers in the data stream have 32-bit precision. + \value DoublePrecision All floating point numbers in the data stream have 64-bit precision. + + \sa setFloatingPointPrecision(), floatingPointPrecision() +*/ + +/*! + \enum QDataStream::Status + + This enum describes the current status of the data stream. + + \value Ok The data stream is operating normally. + \value ReadPastEnd The data stream has read past the end of the + data in the underlying device. + \value ReadCorruptData The data stream has read corrupt data. + \value WriteFailed The data stream cannot write to the underlying device. +*/ + +/***************************************************************************** + QDataStream member functions + *****************************************************************************/ + +#undef CHECK_STREAM_PRECOND +#ifndef QT_NO_DEBUG +#define CHECK_STREAM_PRECOND(retVal) \ + if (!dev) { \ + qWarning("QDataStream: No device"); \ + return retVal; \ + } +#else +#define CHECK_STREAM_PRECOND(retVal) \ + if (!dev) { \ + return retVal; \ + } +#endif + +#define CHECK_STREAM_WRITE_PRECOND(retVal) \ + CHECK_STREAM_PRECOND(retVal) \ + if (q_status != Ok) \ + return retVal; + +enum { + DefaultStreamVersion = QDataStream::Qt_4_6 +}; + +// ### 5.0: when streaming invalid QVariants, just the type should +// be written, no "data" after it + +/*! + Constructs a data stream that has no I/O device. + + \sa setDevice() +*/ + +QDataStream::QDataStream() +{ + dev = 0; + owndev = false; + byteorder = BigEndian; + ver = DefaultStreamVersion; + noswap = QSysInfo::ByteOrder == QSysInfo::BigEndian; + q_status = Ok; +} + +/*! + Constructs a data stream that uses the I/O device \a d. + + \warning If you use QSocket or QSocketDevice as the I/O device \a d + for reading data, you must make sure that enough data is available + on the socket for the operation to successfully proceed; + QDataStream does not have any means to handle or recover from + short-reads. + + \sa setDevice(), device() +*/ + +QDataStream::QDataStream(QIODevice *d) +{ + dev = d; // set device + owndev = false; + byteorder = BigEndian; // default byte order + ver = DefaultStreamVersion; + noswap = QSysInfo::ByteOrder == QSysInfo::BigEndian; + q_status = Ok; +} + +#ifdef QT3_SUPPORT +/*! + \fn QDataStream::QDataStream(QByteArray *array, int mode) + \compat + + Constructs a data stream that operates on the given \a array. The + \a mode specifies how the byte array is to be used, and is + usually either QIODevice::ReadOnly or QIODevice::WriteOnly. +*/ +QDataStream::QDataStream(QByteArray *a, int mode) +{ + QBuffer *buf = new QBuffer(a); +#ifndef QT_NO_QOBJECT + buf->blockSignals(true); +#endif + buf->open(QIODevice::OpenMode(mode)); + dev = buf; + owndev = true; + byteorder = BigEndian; + ver = DefaultStreamVersion; + noswap = QSysInfo::ByteOrder == QSysInfo::BigEndian; + q_status = Ok; +} +#endif + +/*! + \fn QDataStream::QDataStream(QByteArray *a, QIODevice::OpenMode mode) + + Constructs a data stream that operates on a byte array, \a a. The + \a mode describes how the device is to be used. + + Alternatively, you can use QDataStream(const QByteArray &) if you + just want to read from a byte array. + + Since QByteArray is not a QIODevice subclass, internally a QBuffer + is created to wrap the byte array. +*/ + +QDataStream::QDataStream(QByteArray *a, QIODevice::OpenMode flags) +{ + QBuffer *buf = new QBuffer(a); +#ifndef QT_NO_QOBJECT + buf->blockSignals(true); +#endif + buf->open(flags); + dev = buf; + owndev = true; + byteorder = BigEndian; + ver = DefaultStreamVersion; + noswap = QSysInfo::ByteOrder == QSysInfo::BigEndian; + q_status = Ok; +} + +/*! + Constructs a read-only data stream that operates on byte array \a a. + Use QDataStream(QByteArray*, int) if you want to write to a byte + array. + + Since QByteArray is not a QIODevice subclass, internally a QBuffer + is created to wrap the byte array. +*/ +QDataStream::QDataStream(const QByteArray &a) +{ + QBuffer *buf = new QBuffer; +#ifndef QT_NO_QOBJECT + buf->blockSignals(true); +#endif + buf->setData(a); + buf->open(QIODevice::ReadOnly); + dev = buf; + owndev = true; + byteorder = BigEndian; + ver = DefaultStreamVersion; + noswap = QSysInfo::ByteOrder == QSysInfo::BigEndian; + q_status = Ok; +} + +/*! + Destroys the data stream. + + The destructor will not affect the current I/O device, unless it is + an internal I/O device (e.g. a QBuffer) processing a QByteArray + passed in the \e constructor, in which case the internal I/O device + is destroyed. +*/ + +QDataStream::~QDataStream() +{ + if (owndev) + delete dev; +} + + +/*! + \fn QIODevice *QDataStream::device() const + + Returns the I/O device currently set, or 0 if no + device is currently set. + + \sa setDevice() +*/ + +/*! + void QDataStream::setDevice(QIODevice *d) + + Sets the I/O device to \a d, which can be 0 + to unset to current I/O device. + + \sa device() +*/ + +void QDataStream::setDevice(QIODevice *d) +{ + if (owndev) { + delete dev; + owndev = false; + } + dev = d; +} + +/*! + \obsolete + Unsets the I/O device. + Use setDevice(0) instead. +*/ + +void QDataStream::unsetDevice() +{ + setDevice(0); +} + + +/*! + \fn bool QDataStream::atEnd() const + + Returns true if the I/O device has reached the end position (end of + the stream or file) or if there is no I/O device set; otherwise + returns false. + + \sa QIODevice::atEnd() +*/ + +bool QDataStream::atEnd() const +{ + return dev ? dev->atEnd() : true; +} + +/*! + Returns the floating point precision of the data stream. + + \since 4.6 + + \sa FloatingPointPrecision setFloatingPointPrecision() +*/ +QDataStream::FloatingPointPrecision QDataStream::floatingPointPrecision() const +{ + return d == 0 ? QDataStream::DoublePrecision : d->floatingPointPrecision; +} + +/*! + Sets the floating point precision of the data stream to \a precision. If the floating point precision is + DoublePrecision and the version of the data stream is Qt_4_6 or higher, all floating point + numbers will be written and read with 64-bit precision. If the floating point precision is + SinglePrecision and the version is Qt_4_6 or higher, all floating point numbers will be written + and read with 32-bit precision. + + For versions prior to Qt_4_6, the precision of floating point numbers in the data stream depends + on the stream operator called. + + The default is DoublePrecision. + + \warning This property must be set to the same value on the object that writes and the object + that reads the data stream. + + \since 4.6 +*/ +void QDataStream::setFloatingPointPrecision(QDataStream::FloatingPointPrecision precision) +{ + if (d == 0) + d.reset(new QDataStreamPrivate()); + d->floatingPointPrecision = precision; +} + +/*! + Returns the status of the data stream. + + \sa Status setStatus() resetStatus() +*/ + +QDataStream::Status QDataStream::status() const +{ + return q_status; +} + +/*! + Resets the status of the data stream. + + \sa Status status() setStatus() +*/ +void QDataStream::resetStatus() +{ + q_status = Ok; +} + +/*! + Sets the status of the data stream to the \a status given. + + Subsequent calls to setStatus() are ignored until resetStatus() + is called. + + \sa Status status() resetStatus() +*/ +void QDataStream::setStatus(Status status) +{ + if (q_status == Ok) + q_status = status; +} + +/*!\fn bool QDataStream::eof() const + + Use atEnd() instead. +*/ + +/*! + \fn int QDataStream::byteOrder() const + + Returns the current byte order setting -- either BigEndian or + LittleEndian. + + \sa setByteOrder() +*/ + +/*! + Sets the serialization byte order to \a bo. + + The \a bo parameter can be QDataStream::BigEndian or + QDataStream::LittleEndian. + + The default setting is big endian. We recommend leaving this + setting unless you have special requirements. + + \sa byteOrder() +*/ + +void QDataStream::setByteOrder(ByteOrder bo) +{ + byteorder = bo; + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) + noswap = (byteorder == BigEndian); + else + noswap = (byteorder == LittleEndian); +} + + +/*! + \fn bool QDataStream::isPrintableData() const + + In Qt 4, this function always returns false. + + \sa setPrintableData() +*/ + +/*! + \fn void QDataStream::setPrintableData(bool enable) + + In Qt 3, this function enabled output in a human-readable + format if \a enable was false. + + In Qt 4, QDataStream no longer provides a human-readable output. + This function does nothing. +*/ + +/*! + \enum QDataStream::Version + + This enum provides symbolic synonyms for the data serialization + format version numbers. + + \value Qt_1_0 Version 1 (Qt 1.x) + \value Qt_2_0 Version 2 (Qt 2.0) + \value Qt_2_1 Version 3 (Qt 2.1, 2.2, 2.3) + \value Qt_3_0 Version 4 (Qt 3.0) + \value Qt_3_1 Version 5 (Qt 3.1, 3.2) + \value Qt_3_3 Version 6 (Qt 3.3) + \value Qt_4_0 Version 7 (Qt 4.0, Qt 4.1) + \value Qt_4_1 Version 7 (Qt 4.0, Qt 4.1) + \value Qt_4_2 Version 8 (Qt 4.2) + \value Qt_4_3 Version 9 (Qt 4.3) + \value Qt_4_4 Version 10 (Qt 4.4) + \value Qt_4_5 Version 11 (Qt 4.5) + \value Qt_4_6 Version 12 (Qt 4.6) + \value Qt_4_7 Same as Qt_4_6. + + \sa setVersion(), version() +*/ + +/*! + \fn int QDataStream::version() const + + Returns the version number of the data serialization format. + + \sa setVersion(), Version +*/ + +/*! + \fn void QDataStream::setVersion(int v) + + Sets the version number of the data serialization format to \a v. + + You don't \e have to set a version if you are using the current + version of Qt, but for your own custom binary formats we + recommend that you do; see \l{Versioning} in the Detailed + Description. + + To accommodate new functionality, the datastream serialization + format of some Qt classes has changed in some versions of Qt. If + you want to read data that was created by an earlier version of + Qt, or write data that can be read by a program that was compiled + with an earlier version of Qt, use this function to modify the + serialization format used by QDataStream. + + \table + \header \i Qt Version \i QDataStream Version + \row \i Qt 4.6 \i 12 + \row \i Qt 4.5 \i 11 + \row \i Qt 4.4 \i 10 + \row \i Qt 4.3 \i 9 + \row \i Qt 4.2 \i 8 + \row \i Qt 4.0, 4.1 \i 7 + \row \i Qt 3.3 \i 6 + \row \i Qt 3.1, 3.2 \i 5 + \row \i Qt 3.0 \i 4 + \row \i Qt 2.1, 2.2, 2.3 \i 3 + \row \i Qt 2.0 \i 2 + \row \i Qt 1.x \i 1 + \endtable + + The \l Version enum provides symbolic constants for the different + versions of Qt. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qdatastream.cpp 5 + + \sa version(), Version +*/ + +/***************************************************************************** + QDataStream read functions + *****************************************************************************/ + +/*! + \fn QDataStream &QDataStream::operator>>(quint8 &i) + \overload + + Reads an unsigned byte from the stream into \a i, and returns a + reference to the stream. +*/ + +/*! + Reads a signed byte from the stream into \a i, and returns a + reference to the stream. +*/ + +QDataStream &QDataStream::operator>>(qint8 &i) +{ + i = 0; + CHECK_STREAM_PRECOND(*this) + char c; + if (!dev->getChar(&c)) + setStatus(ReadPastEnd); + else + i = qint8(c); + return *this; +} + + +/*! + \fn QDataStream &QDataStream::operator>>(quint16 &i) + \overload + + Reads an unsigned 16-bit integer from the stream into \a i, and + returns a reference to the stream. +*/ + +/*! + \overload + + Reads a signed 16-bit integer from the stream into \a i, and + returns a reference to the stream. +*/ + +QDataStream &QDataStream::operator>>(qint16 &i) +{ + i = 0; + CHECK_STREAM_PRECOND(*this) + if (dev->read((char *)&i, 2) != 2) { + i = 0; + setStatus(ReadPastEnd); + } else { + if (!noswap) { + i = qbswap(i); + } + } + return *this; +} + + +/*! + \fn QDataStream &QDataStream::operator>>(quint32 &i) + \overload + + Reads an unsigned 32-bit integer from the stream into \a i, and + returns a reference to the stream. +*/ + +/*! + \overload + + Reads a signed 32-bit integer from the stream into \a i, and + returns a reference to the stream. +*/ + +QDataStream &QDataStream::operator>>(qint32 &i) +{ + i = 0; + CHECK_STREAM_PRECOND(*this) + if (dev->read((char *)&i, 4) != 4) { + i = 0; + setStatus(ReadPastEnd); + } else { + if (!noswap) { + i = qbswap(i); + } + } + return *this; +} + +/*! + \fn QDataStream &QDataStream::operator>>(quint64 &i) + \overload + + Reads an unsigned 64-bit integer from the stream, into \a i, and + returns a reference to the stream. +*/ + +/*! + \overload + + Reads a signed 64-bit integer from the stream into \a i, and + returns a reference to the stream. +*/ + +QDataStream &QDataStream::operator>>(qint64 &i) +{ + i = qint64(0); + CHECK_STREAM_PRECOND(*this) + if (version() < 6) { + quint32 i1, i2; + *this >> i2 >> i1; + i = ((quint64)i1 << 32) + i2; + } else { + if (dev->read((char *)&i, 8) != 8) { + i = qint64(0); + setStatus(ReadPastEnd); + } else { + if (!noswap) { + i = qbswap(i); + } + } + } + return *this; +} + +/*! + Reads a boolean value from the stream into \a i. Returns a + reference to the stream. +*/ +QDataStream &QDataStream::operator>>(bool &i) +{ + qint8 v; + *this >> v; + i = !!v; + return *this; +} + +/*! + \overload + + Reads a floating point number from the stream into \a f, + using the standard IEEE 754 format. Returns a reference to the + stream. + + \sa setFloatingPointPrecision() +*/ + +QDataStream &QDataStream::operator>>(float &f) +{ + if (version() >= QDataStream::Qt_4_6 + && floatingPointPrecision() == QDataStream::DoublePrecision) { + double d; + *this >> d; + f = d; + return *this; + } + + f = 0.0f; + CHECK_STREAM_PRECOND(*this) + if (dev->read((char *)&f, 4) != 4) { + f = 0.0f; + setStatus(ReadPastEnd); + } else { + if (!noswap) { + union { + float val1; + quint32 val2; + } x; + x.val2 = qbswap(*reinterpret_cast<quint32 *>(&f)); + f = x.val1; + } + } + return *this; +} + +#if defined(Q_DOUBLE_FORMAT) +#define Q_DF(x) Q_DOUBLE_FORMAT[(x)] - '0' +#endif + +/*! + \overload + + Reads a floating point number from the stream into \a f, + using the standard IEEE 754 format. Returns a reference to the + stream. + + \sa setFloatingPointPrecision() +*/ + +QDataStream &QDataStream::operator>>(double &f) +{ + if (version() >= QDataStream::Qt_4_6 + && floatingPointPrecision() == QDataStream::SinglePrecision) { + float d; + *this >> d; + f = d; + return *this; + } + + f = 0.0; + CHECK_STREAM_PRECOND(*this) +#ifndef Q_DOUBLE_FORMAT + if (dev->read((char *)&f, 8) != 8) { + f = 0.0; + setStatus(ReadPastEnd); + } else { + if (!noswap) { + union { + double val1; + quint64 val2; + } x; + x.val2 = qbswap(*reinterpret_cast<quint64 *>(&f)); + f = x.val1; + } + } +#else + //non-standard floating point format + union { + double val1; + char val2[8]; + } x; + char *p = x.val2; + char b[8]; + if (dev->read(b, 8) == 8) { + if (noswap) { + *p++ = b[Q_DF(0)]; + *p++ = b[Q_DF(1)]; + *p++ = b[Q_DF(2)]; + *p++ = b[Q_DF(3)]; + *p++ = b[Q_DF(4)]; + *p++ = b[Q_DF(5)]; + *p++ = b[Q_DF(6)]; + *p = b[Q_DF(7)]; + } else { + *p++ = b[Q_DF(7)]; + *p++ = b[Q_DF(6)]; + *p++ = b[Q_DF(5)]; + *p++ = b[Q_DF(4)]; + *p++ = b[Q_DF(3)]; + *p++ = b[Q_DF(2)]; + *p++ = b[Q_DF(1)]; + *p = b[Q_DF(0)]; + } + f = x.val1; + } else { + setStatus(ReadPastEnd); + } +#endif + return *this; +} + + +/*! + \overload + + Reads the '\0'-terminated string \a s from the stream and returns + a reference to the stream. + + Space for the string is allocated using \c new -- the caller must + destroy it with \c{delete[]}. +*/ + +QDataStream &QDataStream::operator>>(char *&s) +{ + uint len = 0; + return readBytes(s, len); +} + + +/*! + Reads the buffer \a s from the stream and returns a reference to + the stream. + + The buffer \a s is allocated using \c new. Destroy it with the \c + delete[] operator. + + The \a l parameter is set to the length of the buffer. If the + string read is empty, \a l is set to 0 and \a s is set to + a null pointer. + + The serialization format is a quint32 length specifier first, + then \a l bytes of data. + + \sa readRawData(), writeBytes() +*/ + +QDataStream &QDataStream::readBytes(char *&s, uint &l) +{ + s = 0; + l = 0; + CHECK_STREAM_PRECOND(*this) + + quint32 len; + *this >> len; + if (len == 0) + return *this; + + const quint32 Step = 1024 * 1024; + quint32 allocated = 0; + char *prevBuf = 0; + char *curBuf = 0; + + do { + int blockSize = qMin(Step, len - allocated); + prevBuf = curBuf; + curBuf = new char[allocated + blockSize + 1]; + if (prevBuf) { + memcpy(curBuf, prevBuf, allocated); + delete [] prevBuf; + } + if (dev->read(curBuf + allocated, blockSize) != blockSize) { + delete [] curBuf; + setStatus(ReadPastEnd); + return *this; + } + allocated += blockSize; + } while (allocated < len); + + s = curBuf; + s[len] = '\0'; + l = (uint)len; + return *this; +} + +/*! + Reads at most \a len bytes from the stream into \a s and returns the number of + bytes read. If an error occurs, this function returns -1. + + The buffer \a s must be preallocated. The data is \e not encoded. + + \sa readBytes(), QIODevice::read(), writeRawData() +*/ + +int QDataStream::readRawData(char *s, int len) +{ + CHECK_STREAM_PRECOND(-1) + return dev->read(s, len); +} + + +/***************************************************************************** + QDataStream write functions + *****************************************************************************/ + + +/*! + \fn QDataStream &QDataStream::operator<<(quint8 i) + \overload + + Writes an unsigned byte, \a i, to the stream and returns a + reference to the stream. +*/ + +/*! + Writes a signed byte, \a i, to the stream and returns a reference + to the stream. +*/ + +QDataStream &QDataStream::operator<<(qint8 i) +{ + CHECK_STREAM_WRITE_PRECOND(*this) + if (!dev->putChar(i)) + q_status = WriteFailed; + return *this; +} + + +/*! + \fn QDataStream &QDataStream::operator<<(quint16 i) + \overload + + Writes an unsigned 16-bit integer, \a i, to the stream and returns + a reference to the stream. +*/ + +/*! + \overload + + Writes a signed 16-bit integer, \a i, to the stream and returns a + reference to the stream. +*/ + +QDataStream &QDataStream::operator<<(qint16 i) +{ + CHECK_STREAM_WRITE_PRECOND(*this) + if (!noswap) { + i = qbswap(i); + } + if (dev->write((char *)&i, sizeof(qint16)) != sizeof(qint16)) + q_status = WriteFailed; + return *this; +} + +/*! + \overload + + Writes a signed 32-bit integer, \a i, to the stream and returns a + reference to the stream. +*/ + +QDataStream &QDataStream::operator<<(qint32 i) +{ + CHECK_STREAM_WRITE_PRECOND(*this) + if (!noswap) { + i = qbswap(i); + } + if (dev->write((char *)&i, sizeof(qint32)) != sizeof(qint32)) + q_status = WriteFailed; + return *this; +} + +/*! + \fn QDataStream &QDataStream::operator<<(quint64 i) + \overload + + Writes an unsigned 64-bit integer, \a i, to the stream and returns a + reference to the stream. +*/ + +/*! + \overload + + Writes a signed 64-bit integer, \a i, to the stream and returns a + reference to the stream. +*/ + +QDataStream &QDataStream::operator<<(qint64 i) +{ + CHECK_STREAM_WRITE_PRECOND(*this) + if (version() < 6) { + quint32 i1 = i & 0xffffffff; + quint32 i2 = i >> 32; + *this << i2 << i1; + } else { + if (!noswap) { + i = qbswap(i); + } + if (dev->write((char *)&i, sizeof(qint64)) != sizeof(qint64)) + q_status = WriteFailed; + } + return *this; +} + +/*! + \fn QDataStream &QDataStream::operator<<(quint32 i) + \overload + + Writes an unsigned integer, \a i, to the stream as a 32-bit + unsigned integer (quint32). Returns a reference to the stream. +*/ + +/*! + Writes a boolean value, \a i, to the stream. Returns a reference + to the stream. +*/ + +QDataStream &QDataStream::operator<<(bool i) +{ + CHECK_STREAM_WRITE_PRECOND(*this) + if (!dev->putChar(qint8(i))) + q_status = WriteFailed; + return *this; +} + +/*! + \overload + + Writes a floating point number, \a f, to the stream using + the standard IEEE 754 format. Returns a reference to the stream. + + \sa setFloatingPointPrecision() +*/ + +QDataStream &QDataStream::operator<<(float f) +{ + if (version() >= QDataStream::Qt_4_6 + && floatingPointPrecision() == QDataStream::DoublePrecision) { + *this << double(f); + return *this; + } + + CHECK_STREAM_WRITE_PRECOND(*this) + float g = f; // fixes float-on-stack problem + if (!noswap) { + union { + float val1; + quint32 val2; + } x; + x.val1 = g; + x.val2 = qbswap(x.val2); + g = x.val1; + } + if (dev->write((char *)&g, sizeof(float)) != sizeof(float)) + q_status = WriteFailed; + return *this; +} + + +/*! + \overload + + Writes a floating point number, \a f, to the stream using + the standard IEEE 754 format. Returns a reference to the stream. + + \sa setFloatingPointPrecision() +*/ + +QDataStream &QDataStream::operator<<(double f) +{ + if (version() >= QDataStream::Qt_4_6 + && floatingPointPrecision() == QDataStream::SinglePrecision) { + *this << float(f); + return *this; + } + + CHECK_STREAM_WRITE_PRECOND(*this) +#ifndef Q_DOUBLE_FORMAT + if (noswap) { + if (dev->write((char *)&f, sizeof(double)) != sizeof(double)) + q_status = WriteFailed; + } else { + union { + double val1; + quint64 val2; + } x; + x.val1 = f; + x.val2 = qbswap(x.val2); + if (dev->write((char *)&x.val2, sizeof(double)) != sizeof(double)) + q_status = WriteFailed; + } +#else + union { + double val1; + char val2[8]; + } x; + x.val1 = f; + char *p = x.val2; + char b[8]; + if (noswap) { + b[Q_DF(0)] = *p++; + b[Q_DF(1)] = *p++; + b[Q_DF(2)] = *p++; + b[Q_DF(3)] = *p++; + b[Q_DF(4)] = *p++; + b[Q_DF(5)] = *p++; + b[Q_DF(6)] = *p++; + b[Q_DF(7)] = *p; + } else { + b[Q_DF(7)] = *p++; + b[Q_DF(6)] = *p++; + b[Q_DF(5)] = *p++; + b[Q_DF(4)] = *p++; + b[Q_DF(3)] = *p++; + b[Q_DF(2)] = *p++; + b[Q_DF(1)] = *p++; + b[Q_DF(0)] = *p; + } + if (dev->write(b, 8) != 8) + q_status = WriteFailed; +#endif + return *this; +} + + +/*! + \overload + + Writes the '\0'-terminated string \a s to the stream and returns a + reference to the stream. + + The string is serialized using writeBytes(). +*/ + +QDataStream &QDataStream::operator<<(const char *s) +{ + if (!s) { + *this << (quint32)0; + return *this; + } + uint len = qstrlen(s) + 1; // also write null terminator + *this << (quint32)len; // write length specifier + writeRawData(s, len); + return *this; +} + + +/*! + Writes the length specifier \a len and the buffer \a s to the + stream and returns a reference to the stream. + + The \a len is serialized as a quint32, followed by \a len bytes + from \a s. Note that the data is \e not encoded. + + \sa writeRawData(), readBytes() +*/ + +QDataStream &QDataStream::writeBytes(const char *s, uint len) +{ + CHECK_STREAM_WRITE_PRECOND(*this) + *this << (quint32)len; // write length specifier + if (len) + writeRawData(s, len); + return *this; +} + + +/*! + Writes \a len bytes from \a s to the stream. Returns the + number of bytes actually written, or -1 on error. + The data is \e not encoded. + + \sa writeBytes(), QIODevice::write(), readRawData() +*/ + +int QDataStream::writeRawData(const char *s, int len) +{ + CHECK_STREAM_WRITE_PRECOND(-1) + int ret = dev->write(s, len); + if (ret != len) + q_status = WriteFailed; + return ret; +} + +/*! + \since 4.1 + + Skips \a len bytes from the device. Returns the number of bytes + actually skipped, or -1 on error. + + This is equivalent to calling readRawData() on a buffer of length + \a len and ignoring the buffer. + + \sa QIODevice::seek() +*/ +int QDataStream::skipRawData(int len) +{ + CHECK_STREAM_PRECOND(-1) + + if (dev->isSequential()) { + char buf[4096]; + int sumRead = 0; + + while (len > 0) { + int blockSize = qMin(len, (int)sizeof(buf)); + int n = dev->read(buf, blockSize); + if (n == -1) + return -1; + if (n == 0) + return sumRead; + + sumRead += n; + len -= blockSize; + } + return sumRead; + } else { + qint64 pos = dev->pos(); + qint64 size = dev->size(); + if (pos + len > size) + len = size - pos; + if (!dev->seek(pos + len)) + return -1; + return len; + } +} + +#ifdef QT3_SUPPORT +/*! + \fn QDataStream &QDataStream::readRawBytes(char *str, uint len) + + Use readRawData() instead. +*/ + +/*! + \fn QDataStream &QDataStream::writeRawBytes(const char *str, uint len) + + Use writeRawData() instead. +*/ +#endif + +QT_END_NAMESPACE + +#endif // QT_NO_DATASTREAM diff --git a/src/corelib/io/qdatastream.h b/src/corelib/io/qdatastream.h new file mode 100644 index 0000000000..d19fcc5377 --- /dev/null +++ b/src/corelib/io/qdatastream.h @@ -0,0 +1,440 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATASTREAM_H +#define QDATASTREAM_H + +#include <QtCore/qscopedpointer.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qglobal.h> + +#ifdef Status +#error qdatastream.h must be included before any header file that defines Status +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QByteArray; +class QIODevice; + +template <typename T> class QList; +template <typename T> class QLinkedList; +template <typename T> class QVector; +template <typename T> class QSet; +template <class Key, class T> class QHash; +template <class Key, class T> class QMap; + +#if !defined(QT_NO_DATASTREAM) || defined(QT_BOOTSTRAPPED) +class QDataStreamPrivate; +class Q_CORE_EXPORT QDataStream +{ +public: + enum Version { + Qt_1_0 = 1, + Qt_2_0 = 2, + Qt_2_1 = 3, + Qt_3_0 = 4, + Qt_3_1 = 5, + Qt_3_3 = 6, + Qt_4_0 = 7, + Qt_4_1 = Qt_4_0, + Qt_4_2 = 8, + Qt_4_3 = 9, + Qt_4_4 = 10, + Qt_4_5 = 11, + Qt_4_6 = 12, + Qt_4_7 = Qt_4_6, + Qt_4_8 = Qt_4_7 +#if QT_VERSION >= 0x040900 +#error Add the datastream version for this Qt version + Qt_4_9 = Qt_4_8 +#endif + }; + + enum ByteOrder { + BigEndian = QSysInfo::BigEndian, + LittleEndian = QSysInfo::LittleEndian + }; + + enum Status { + Ok, + ReadPastEnd, + ReadCorruptData, + WriteFailed + }; + + enum FloatingPointPrecision { + SinglePrecision, + DoublePrecision + }; + + QDataStream(); + explicit QDataStream(QIODevice *); +#ifdef QT3_SUPPORT + QDataStream(QByteArray *, int mode); +#endif + QDataStream(QByteArray *, QIODevice::OpenMode flags); + QDataStream(const QByteArray &); + virtual ~QDataStream(); + + QIODevice *device() const; + void setDevice(QIODevice *); + void unsetDevice(); + + bool atEnd() const; +#ifdef QT3_SUPPORT + inline QT3_SUPPORT bool eof() const { return atEnd(); } +#endif + + Status status() const; + void setStatus(Status status); + void resetStatus(); + + FloatingPointPrecision floatingPointPrecision() const; + void setFloatingPointPrecision(FloatingPointPrecision precision); + + ByteOrder byteOrder() const; + void setByteOrder(ByteOrder); + + int version() const; + void setVersion(int); + + QDataStream &operator>>(qint8 &i); + QDataStream &operator>>(quint8 &i); + QDataStream &operator>>(qint16 &i); + QDataStream &operator>>(quint16 &i); + QDataStream &operator>>(qint32 &i); + QDataStream &operator>>(quint32 &i); + QDataStream &operator>>(qint64 &i); + QDataStream &operator>>(quint64 &i); + + QDataStream &operator>>(bool &i); + QDataStream &operator>>(float &f); + QDataStream &operator>>(double &f); + QDataStream &operator>>(char *&str); + + QDataStream &operator<<(qint8 i); + QDataStream &operator<<(quint8 i); + QDataStream &operator<<(qint16 i); + QDataStream &operator<<(quint16 i); + QDataStream &operator<<(qint32 i); + QDataStream &operator<<(quint32 i); + QDataStream &operator<<(qint64 i); + QDataStream &operator<<(quint64 i); + QDataStream &operator<<(bool i); + QDataStream &operator<<(float f); + QDataStream &operator<<(double f); + QDataStream &operator<<(const char *str); + + QDataStream &readBytes(char *&, uint &len); + int readRawData(char *, int len); + + QDataStream &writeBytes(const char *, uint len); + int writeRawData(const char *, int len); + + int skipRawData(int len); + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT QDataStream &readRawBytes(char *str, uint len) + { readRawData(str, static_cast<int>(len)); return *this; } + inline QT3_SUPPORT QDataStream &writeRawBytes(const char *str, uint len) + { writeRawData(str, static_cast<int>(len)); return *this; } + inline QT3_SUPPORT bool isPrintableData() const { return false; } + inline QT3_SUPPORT void setPrintableData(bool) {} +#endif + +private: + Q_DISABLE_COPY(QDataStream) + + QScopedPointer<QDataStreamPrivate> d; + + QIODevice *dev; + bool owndev; + bool noswap; + ByteOrder byteorder; + int ver; + Status q_status; +}; + + +/***************************************************************************** + QDataStream inline functions + *****************************************************************************/ + +inline QIODevice *QDataStream::device() const +{ return dev; } + +inline QDataStream::ByteOrder QDataStream::byteOrder() const +{ return byteorder; } + +inline int QDataStream::version() const +{ return ver; } + +inline void QDataStream::setVersion(int v) +{ ver = v; } + +inline QDataStream &QDataStream::operator>>(quint8 &i) +{ return *this >> reinterpret_cast<qint8&>(i); } + +inline QDataStream &QDataStream::operator>>(quint16 &i) +{ return *this >> reinterpret_cast<qint16&>(i); } + +inline QDataStream &QDataStream::operator>>(quint32 &i) +{ return *this >> reinterpret_cast<qint32&>(i); } + +inline QDataStream &QDataStream::operator>>(quint64 &i) +{ return *this >> reinterpret_cast<qint64&>(i); } + +inline QDataStream &QDataStream::operator<<(quint8 i) +{ return *this << qint8(i); } + +inline QDataStream &QDataStream::operator<<(quint16 i) +{ return *this << qint16(i); } + +inline QDataStream &QDataStream::operator<<(quint32 i) +{ return *this << qint32(i); } + +inline QDataStream &QDataStream::operator<<(quint64 i) +{ return *this << qint64(i); } + +template <typename T> +QDataStream& operator>>(QDataStream& s, QList<T>& l) +{ + l.clear(); + quint32 c; + s >> c; + l.reserve(c); + for(quint32 i = 0; i < c; ++i) + { + T t; + s >> t; + l.append(t); + if (s.atEnd()) + break; + } + return s; +} + +template <typename T> +QDataStream& operator<<(QDataStream& s, const QList<T>& l) +{ + s << quint32(l.size()); + for (int i = 0; i < l.size(); ++i) + s << l.at(i); + return s; +} + +template <typename T> +QDataStream& operator>>(QDataStream& s, QLinkedList<T>& l) +{ + l.clear(); + quint32 c; + s >> c; + for(quint32 i = 0; i < c; ++i) + { + T t; + s >> t; + l.append(t); + if (s.atEnd()) + break; + } + return s; +} + +template <typename T> +QDataStream& operator<<(QDataStream& s, const QLinkedList<T>& l) +{ + s << quint32(l.size()); + typename QLinkedList<T>::ConstIterator it = l.constBegin(); + for(; it != l.constEnd(); ++it) + s << *it; + return s; +} + +template<typename T> +QDataStream& operator>>(QDataStream& s, QVector<T>& v) +{ + v.clear(); + quint32 c; + s >> c; + v.resize(c); + for(quint32 i = 0; i < c; ++i) { + T t; + s >> t; + v[i] = t; + } + return s; +} + +template<typename T> +QDataStream& operator<<(QDataStream& s, const QVector<T>& v) +{ + s << quint32(v.size()); + for (typename QVector<T>::const_iterator it = v.begin(); it != v.end(); ++it) + s << *it; + return s; +} + +template <typename T> +QDataStream &operator>>(QDataStream &in, QSet<T> &set) +{ + set.clear(); + quint32 c; + in >> c; + for (quint32 i = 0; i < c; ++i) { + T t; + in >> t; + set << t; + if (in.atEnd()) + break; + } + return in; +} + +template <typename T> +QDataStream& operator<<(QDataStream &out, const QSet<T> &set) +{ + out << quint32(set.size()); + typename QSet<T>::const_iterator i = set.constBegin(); + while (i != set.constEnd()) { + out << *i; + ++i; + } + return out; +} + +template <class Key, class T> +Q_OUTOFLINE_TEMPLATE QDataStream &operator>>(QDataStream &in, QHash<Key, T> &hash) +{ + QDataStream::Status oldStatus = in.status(); + in.resetStatus(); + hash.clear(); + + quint32 n; + in >> n; + + for (quint32 i = 0; i < n; ++i) { + if (in.status() != QDataStream::Ok) + break; + + Key k; + T t; + in >> k >> t; + hash.insertMulti(k, t); + } + + if (in.status() != QDataStream::Ok) + hash.clear(); + if (oldStatus != QDataStream::Ok) + in.setStatus(oldStatus); + return in; +} + +template <class Key, class T> +Q_OUTOFLINE_TEMPLATE QDataStream &operator<<(QDataStream &out, const QHash<Key, T>& hash) +{ + out << quint32(hash.size()); + typename QHash<Key, T>::ConstIterator it = hash.end(); + typename QHash<Key, T>::ConstIterator begin = hash.begin(); + while (it != begin) { + --it; + out << it.key() << it.value(); + } + return out; +} +#ifdef qdoc +template <class Key, class T> +Q_OUTOFLINE_TEMPLATE QDataStream &operator>>(QDataStream &in, QMap<Key, T> &map) +#else +template <class aKey, class aT> +Q_OUTOFLINE_TEMPLATE QDataStream &operator>>(QDataStream &in, QMap<aKey, aT> &map) +#endif +{ + QDataStream::Status oldStatus = in.status(); + in.resetStatus(); + map.clear(); + + quint32 n; + in >> n; + + map.detach(); + map.setInsertInOrder(true); + for (quint32 i = 0; i < n; ++i) { + if (in.status() != QDataStream::Ok) + break; + + aKey key; + aT value; + in >> key >> value; + map.insertMulti(key, value); + } + map.setInsertInOrder(false); + if (in.status() != QDataStream::Ok) + map.clear(); + if (oldStatus != QDataStream::Ok) + in.setStatus(oldStatus); + return in; +} + +template <class Key, class T> +Q_OUTOFLINE_TEMPLATE QDataStream &operator<<(QDataStream &out, const QMap<Key, T> &map) +{ + out << quint32(map.size()); + typename QMap<Key, T>::ConstIterator it = map.end(); + typename QMap<Key, T>::ConstIterator begin = map.begin(); + while (it != begin) { + --it; + out << it.key() << it.value(); + } + return out; +} + +#endif // QT_NO_DATASTREAM + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDATASTREAM_H diff --git a/src/corelib/io/qdatastream_p.h b/src/corelib/io/qdatastream_p.h new file mode 100644 index 0000000000..ec270d2296 --- /dev/null +++ b/src/corelib/io/qdatastream_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATASTREAM_P_H +#define QDATASTREAM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qdatastream.h> + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_DATASTREAM) || defined(QT_BOOTSTRAPPED) +class QDataStreamPrivate +{ +public: + QDataStreamPrivate() : floatingPointPrecision(QDataStream::DoublePrecision) { } + + QDataStream::FloatingPointPrecision floatingPointPrecision; +}; +#endif + +QT_END_NAMESPACE + +#endif // QDATASTREAM_P_H diff --git a/src/corelib/io/qdataurl.cpp b/src/corelib/io/qdataurl.cpp new file mode 100644 index 0000000000..2e6f635886 --- /dev/null +++ b/src/corelib/io/qdataurl.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qurl.h" +#include "private/qdataurl_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \internal + + Decode a data: URL into its mimetype and payload. Returns a null string if + the URL could not be decoded. +*/ +Q_CORE_EXPORT QPair<QString, QByteArray> qDecodeDataUrl(const QUrl &uri) +{ + QString mimeType; + QByteArray payload; + + if (uri.scheme() == QLatin1String("data") && uri.host().isEmpty()) { + mimeType = QLatin1String("text/plain;charset=US-ASCII"); + + // the following would have been the correct thing, but + // reality often differs from the specification. People have + // data: URIs with ? and # + //QByteArray data = QByteArray::fromPercentEncoding(uri.encodedPath()); + QByteArray data = QByteArray::fromPercentEncoding(uri.toEncoded()); + + // remove the data: scheme + data.remove(0, 5); + + // parse it: + int pos = data.indexOf(','); + if (pos != -1) { + payload = data.mid(pos + 1); + data.truncate(pos); + data = data.trimmed(); + + // find out if the payload is encoded in Base64 + if (data.endsWith(";base64")) { + payload = QByteArray::fromBase64(payload); + data.chop(7); + } + + if (data.toLower().startsWith("charset")) { + int i = 7; // strlen("charset") + while (data.at(i) == ' ') + ++i; + if (data.at(i) == '=') + data.prepend("text/plain;"); + } + + if (!data.isEmpty()) + mimeType = QLatin1String(data.trimmed()); + + } + } + + return QPair<QString,QByteArray>(mimeType,payload); +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qdataurl_p.h b/src/corelib/io/qdataurl_p.h new file mode 100644 index 0000000000..0cde476a5f --- /dev/null +++ b/src/corelib/io/qdataurl_p.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATAURL_P_H +#define QDATAURL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qDecodeDataUrl. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qurl.h" +#include "QtCore/qbytearray.h" +#include "QtCore/qstring.h" +#include "QtCore/qpair.h" + +QT_BEGIN_NAMESPACE + +Q_CORE_EXPORT QPair<QString, QByteArray> qDecodeDataUrl(const QUrl &url); + +QT_END_NAMESPACE + +#endif // QDATAURL_P_H diff --git a/src/corelib/io/qdebug.cpp b/src/corelib/io/qdebug.cpp new file mode 100644 index 0000000000..aac0d982aa --- /dev/null +++ b/src/corelib/io/qdebug.cpp @@ -0,0 +1,307 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifdef QT_NO_DEBUG +#undef QT_NO_DEBUG +#endif +#ifdef qDebug +#undef qDebug +#endif + +#include "qdebug.h" + +// This file is needed to force compilation of QDebug into the kernel library. + +/*! + \class QDebug + + \brief The QDebug class provides an output stream for debugging information. + + QDebug is used whenever the developer needs to write out debugging or tracing + information to a device, file, string or console. + + \section1 Basic Use + + In the common case, it is useful to call the qDebug() function to obtain a + default QDebug object to use for writing debugging information. + + \snippet doc/src/snippets/qdebug/qdebugsnippet.cpp 1 + + This constructs a QDebug object using the constructor that accepts a QtMsgType + value of QtDebugMsg. Similarly, the qWarning(), qCritical() and qFatal() + functions also return QDebug objects for the corresponding message types. + + The class also provides several constructors for other situations, including + a constructor that accepts a QFile or any other QIODevice subclass that is + used to write debugging information to files and other devices. The constructor + that accepts a QString is used to write to a string for display or serialization. + + \section1 Writing Custom Types to a Stream + + Many standard types can be written to QDebug objects, and Qt provides support for + most Qt value types. To add support for custom types, you need to implement a + streaming operator, as in the following example: + + \snippet doc/src/snippets/qdebug/qdebugsnippet.cpp 0 + + This is described in the \l{Debugging Techniques} and + \l{Creating Custom Qt Types#Making the Type Printable}{Creating Custom Qt Types} + documents. +*/ + +/*! + \fn QDebug::QDebug(QIODevice *device) + + Constructs a debug stream that writes to the given \a device. +*/ + +/*! + \fn QDebug::QDebug(QString *string) + + Constructs a debug stream that writes to the given \a string. +*/ + +/*! + \fn QDebug::QDebug(QtMsgType type) + + Constructs a debug stream that writes to the handler for the message type specified by \a type. +*/ + +/*! + \fn QDebug::QDebug(const QDebug &other) + + Constructs a copy of the \a other debug stream. +*/ + +/*! + \fn QDebug &QDebug::operator=(const QDebug &other) + + Assigns the \a other debug stream to this stream and returns a reference to + this stream. +*/ + +/*! + \fn QDebug::~QDebug() + + Flushes any pending data to be written and destroys the debug stream. +*/ + +/*! + \fn QDebug &QDebug::space() + + Writes a space character to the debug stream and returns a reference to + the stream. + + The stream will record that the last character sent to the stream was a + space. + + \sa nospace(), maybeSpace() +*/ + +/*! + \fn QDebug &QDebug::nospace() + + Clears the stream's internal flag that records whether the last character + was a space and returns a reference to the stream. + + \sa space(), maybeSpace() +*/ + +/*! + \fn QDebug &QDebug::maybeSpace() + + Writes a space character to the debug stream, depending on the last + character sent to the stream, and returns a reference to the stream. + + If the last character was a space character, this function writes a space + character to the stream; otherwise, no characters are written to the stream. + + \sa space(), nospace() +*/ + +/*! + \fn QDebug &QDebug::operator<<(QChar t) + + Writes the character, \a t, to the stream and returns a reference to the + stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(QBool t) + \internal + + Writes the boolean value, \a t, to the stream and returns a reference to the + stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(bool t) + + Writes the boolean value, \a t, to the stream and returns a reference to the + stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(char t) + + Writes the character, \a t, to the stream and returns a reference to the + stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(signed short i) + + Writes the signed short integer, \a i, to the stream and returns a reference + to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(unsigned short i) + + Writes then unsigned short integer, \a i, to the stream and returns a + reference to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(signed int i) + + Writes the signed integer, \a i, to the stream and returns a reference + to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(unsigned int i) + + Writes then unsigned integer, \a i, to the stream and returns a reference to + the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(signed long l) + + Writes the signed long integer, \a l, to the stream and returns a reference + to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(unsigned long l) + + Writes then unsigned long integer, \a l, to the stream and returns a reference + to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(qint64 i) + + Writes the signed 64-bit integer, \a i, to the stream and returns a reference + to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(quint64 i) + + Writes then unsigned 64-bit integer, \a i, to the stream and returns a + reference to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(float f) + + Writes the 32-bit floating point number, \a f, to the stream and returns a + reference to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(double f) + + Writes the 64-bit floating point number, \a f, to the stream and returns a + reference to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(const char *s) + + Writes the '\0'-terminated string, \a s, to the stream and returns a + reference to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(const QString &s) + + Writes the string, \a s, to the stream and returns a reference to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(const QStringRef &s) + + Writes the string reference, \a s, to the stream and returns a reference to + the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(const QLatin1String &s) + + Writes the Latin1-encoded string, \a s, to the stream and returns a reference + to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(const QByteArray &b) + + Writes the byte array, \a b, to the stream and returns a reference to the + stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(const void *p) + + Writes a pointer, \a p, to the stream and returns a reference to the stream. +*/ + +/*! + \fn QDebug &QDebug::operator<<(QTextStreamFunction f) + \internal +*/ + +/*! + \fn QDebug &QDebug::operator<<(QTextStreamManipulator m) + \internal +*/ diff --git a/src/corelib/io/qdebug.h b/src/corelib/io/qdebug.h new file mode 100644 index 0000000000..f463b753fb --- /dev/null +++ b/src/corelib/io/qdebug.h @@ -0,0 +1,300 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDEBUG_H +#define QDEBUG_H + +#include <QtCore/qalgorithms.h> +#include <QtCore/qhash.h> +#include <QtCore/qlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qpair.h> +#include <QtCore/qtextstream.h> +#include <QtCore/qstring.h> +#include <QtCore/qvector.h> +#include <QtCore/qset.h> +#include <QtCore/qcontiguouscache.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class Q_CORE_EXPORT QDebug +{ + struct Stream { + Stream(QIODevice *device) : ts(device), ref(1), type(QtDebugMsg), space(true), message_output(false) {} + Stream(QString *string) : ts(string, QIODevice::WriteOnly), ref(1), type(QtDebugMsg), space(true), message_output(false) {} + Stream(QtMsgType t) : ts(&buffer, QIODevice::WriteOnly), ref(1), type(t), space(true), message_output(true) {} + QTextStream ts; + QString buffer; + int ref; + QtMsgType type; + bool space; + bool message_output; + } *stream; +public: + inline QDebug(QIODevice *device) : stream(new Stream(device)) {} + inline QDebug(QString *string) : stream(new Stream(string)) {} + inline QDebug(QtMsgType t) : stream(new Stream(t)) {} + inline QDebug(const QDebug &o):stream(o.stream) { ++stream->ref; } + inline QDebug &operator=(const QDebug &other); + inline ~QDebug() { + if (!--stream->ref) { + if(stream->message_output) { + QT_TRY { + qt_message_output(stream->type, stream->buffer.toLocal8Bit().data()); + } QT_CATCH(std::bad_alloc&) { /* We're out of memory - give up. */ } + } + delete stream; + } + } + inline QDebug &space() { stream->space = true; stream->ts << ' '; return *this; } + inline QDebug &nospace() { stream->space = false; return *this; } + inline QDebug &maybeSpace() { if (stream->space) stream->ts << ' '; return *this; } + + inline QDebug &operator<<(QChar t) { stream->ts << '\'' << t << '\''; return maybeSpace(); } + inline QDebug &operator<<(QBool t) { stream->ts << (bool(t != 0) ? "true" : "false"); return maybeSpace(); } + inline QDebug &operator<<(bool t) { stream->ts << (t ? "true" : "false"); return maybeSpace(); } + inline QDebug &operator<<(char t) { stream->ts << t; return maybeSpace(); } + inline QDebug &operator<<(signed short t) { stream->ts << t; return maybeSpace(); } + inline QDebug &operator<<(unsigned short t) { stream->ts << t; return maybeSpace(); } + inline QDebug &operator<<(signed int t) { stream->ts << t; return maybeSpace(); } + inline QDebug &operator<<(unsigned int t) { stream->ts << t; return maybeSpace(); } + inline QDebug &operator<<(signed long t) { stream->ts << t; return maybeSpace(); } + inline QDebug &operator<<(unsigned long t) { stream->ts << t; return maybeSpace(); } + inline QDebug &operator<<(qint64 t) + { stream->ts << QString::number(t); return maybeSpace(); } + inline QDebug &operator<<(quint64 t) + { stream->ts << QString::number(t); return maybeSpace(); } + inline QDebug &operator<<(float t) { stream->ts << t; return maybeSpace(); } + inline QDebug &operator<<(double t) { stream->ts << t; return maybeSpace(); } + inline QDebug &operator<<(const char* t) { stream->ts << QString::fromAscii(t); return maybeSpace(); } + inline QDebug &operator<<(const QString & t) { stream->ts << '\"' << t << '\"'; return maybeSpace(); } + inline QDebug &operator<<(const QStringRef & t) { return operator<<(t.toString()); } + inline QDebug &operator<<(const QLatin1String &t) { stream->ts << '\"' << t.latin1() << '\"'; return maybeSpace(); } + inline QDebug &operator<<(const QByteArray & t) { stream->ts << '\"' << t << '\"'; return maybeSpace(); } + inline QDebug &operator<<(const void * t) { stream->ts << t; return maybeSpace(); } + inline QDebug &operator<<(QTextStreamFunction f) { + stream->ts << f; + return *this; + } + + inline QDebug &operator<<(QTextStreamManipulator m) + { stream->ts << m; return *this; } +}; + +class QNoDebug +{ +public: + inline QNoDebug(){} + inline QNoDebug(const QDebug &){} + inline ~QNoDebug(){} +#if !defined( QT_NO_TEXTSTREAM ) + inline QNoDebug &operator<<(QTextStreamFunction) { return *this; } + inline QNoDebug &operator<<(QTextStreamManipulator) { return *this; } +#endif + inline QNoDebug &space() { return *this; } + inline QNoDebug &nospace() { return *this; } + inline QNoDebug &maybeSpace() { return *this; } + + template<typename T> + inline QNoDebug &operator<<(const T &) { return *this; } +}; + +Q_CORE_EXPORT_INLINE QDebug qCritical() { return QDebug(QtCriticalMsg); } + +inline QDebug &QDebug::operator=(const QDebug &other) +{ + if (this != &other) { + QDebug copy(other); + qSwap(stream, copy.stream); + } + return *this; +} + +#if defined(FORCE_UREF) +template <class T> +inline QDebug &operator<<(QDebug debug, const QList<T> &list) +#else +template <class T> +inline QDebug operator<<(QDebug debug, const QList<T> &list) +#endif +{ + debug.nospace() << '('; + for (Q_TYPENAME QList<T>::size_type i = 0; i < list.count(); ++i) { + if (i) + debug << ", "; + debug << list.at(i); + } + debug << ')'; + return debug.space(); +} + +#if defined(FORCE_UREF) +template <typename T> +inline QDebug &operator<<(QDebug debug, const QVector<T> &vec) +#else +template <typename T> +inline QDebug operator<<(QDebug debug, const QVector<T> &vec) +#endif +{ + debug.nospace() << "QVector"; + return operator<<(debug, vec.toList()); +} + +#if defined(FORCE_UREF) +template <class aKey, class aT> +inline QDebug &operator<<(QDebug debug, const QMap<aKey, aT> &map) +#else +template <class aKey, class aT> +inline QDebug operator<<(QDebug debug, const QMap<aKey, aT> &map) +#endif +{ + debug.nospace() << "QMap("; + for (typename QMap<aKey, aT>::const_iterator it = map.constBegin(); + it != map.constEnd(); ++it) { + debug << '(' << it.key() << ", " << it.value() << ')'; + } + debug << ')'; + return debug.space(); +} + +#if defined(FORCE_UREF) +template <class aKey, class aT> +inline QDebug &operator<<(QDebug debug, const QHash<aKey, aT> &hash) +#else +template <class aKey, class aT> +inline QDebug operator<<(QDebug debug, const QHash<aKey, aT> &hash) +#endif +{ + debug.nospace() << "QHash("; + for (typename QHash<aKey, aT>::const_iterator it = hash.constBegin(); + it != hash.constEnd(); ++it) + debug << '(' << it.key() << ", " << it.value() << ')'; + debug << ')'; + return debug.space(); +} + +#if defined(FORCE_UREF) +template <class T1, class T2> +inline QDebug &operator<<(QDebug debug, const QPair<T1, T2> &pair) +#else +template <class T1, class T2> +inline QDebug operator<<(QDebug debug, const QPair<T1, T2> &pair) +#endif +{ + debug.nospace() << "QPair(" << pair.first << ',' << pair.second << ')'; + return debug.space(); +} + +template <typename T> +inline QDebug operator<<(QDebug debug, const QSet<T> &set) +{ + debug.nospace() << "QSet"; + return operator<<(debug, set.toList()); +} + +#if defined(FORCE_UREF) +template <class T> +inline QDebug &operator<<(QDebug debug, const QContiguousCache<T> &cache) +#else +template <class T> +inline QDebug operator<<(QDebug debug, const QContiguousCache<T> &cache) +#endif +{ + debug.nospace() << "QContiguousCache("; + for (int i = cache.firstIndex(); i <= cache.lastIndex(); ++i) { + debug << cache[i]; + if (i != cache.lastIndex()) + debug << ", "; + } + debug << ')'; + return debug.space(); +} + +#if defined(FORCE_UREF) +template <class T> +inline QDebug &operator<<(QDebug debug, const QFlags<T> &flags) +#else +template <class T> +inline QDebug operator<<(QDebug debug, const QFlags<T> &flags) +#endif +{ + debug.nospace() << "QFlags("; + bool needSeparator = false; + for (uint i = 0; i < sizeof(T) * 8; ++i) { + if (flags.testFlag(T(1 << i))) { + if (needSeparator) + debug.nospace() << '|'; + else + needSeparator = true; + debug.nospace() << "0x" << QByteArray::number(T(1 << i), 16).constData(); + } + } + debug << ')'; + return debug.space(); +} + +#if !defined(QT_NO_DEBUG_STREAM) +Q_CORE_EXPORT_INLINE QDebug qDebug() { return QDebug(QtDebugMsg); } + +#else // QT_NO_DEBUG_STREAM +#undef qDebug +inline QNoDebug qDebug() { return QNoDebug(); } +#define qDebug QT_NO_QDEBUG_MACRO + +#endif + +#if !defined(QT_NO_WARNING_OUTPUT) +Q_CORE_EXPORT_INLINE QDebug qWarning() { return QDebug(QtWarningMsg); } +#else +#undef qWarning +inline QNoDebug qWarning() { return QNoDebug(); } +#define qWarning QT_NO_QWARNING_MACRO +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDEBUG_H diff --git a/src/corelib/io/qdir.cpp b/src/corelib/io/qdir.cpp new file mode 100644 index 0000000000..166513a6ff --- /dev/null +++ b/src/corelib/io/qdir.cpp @@ -0,0 +1,2386 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qdir.h" +#include "qdir_p.h" +#include "qabstractfileengine.h" +#ifndef QT_NO_DEBUG_STREAM +#include "qdebug.h" +#endif +#include "qdiriterator.h" +#include "qfsfileengine.h" +#include "qdatetime.h" +#include "qstring.h" +#include "qregexp.h" +#include "qvector.h" +#include "qalgorithms.h" +#include "qvarlengtharray.h" +#include "qfilesystementry_p.h" +#include "qfilesystemmetadata_p.h" +#include "qfilesystemengine_p.h" +#include <qstringbuilder.h> + +#ifdef QT_BUILD_CORE_LIB +# include "qresource.h" +# include "private/qcoreglobaldata_p.h" +#endif + +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +static QString driveSpec(const QString &path) +{ +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (path.size() < 2) + return QString(); + char c = path.at(0).toAscii(); + if (c < 'a' && c > 'z' && c < 'A' && c > 'Z') + return QString(); + if (path.at(1).toAscii() != ':') + return QString(); + return path.mid(0, 2); +#else + Q_UNUSED(path); + return QString(); +#endif +} + +//************* QDirPrivate +QDirPrivate::QDirPrivate(const QString &path, const QStringList &nameFilters_, QDir::SortFlags sort_, QDir::Filters filters_) + : QSharedData() + , nameFilters(nameFilters_) + , sort(sort_) + , filters(filters_) +#ifdef QT3_SUPPORT + , filterSepChar(0) + , matchAllDirs(false) +#endif + , fileListsInitialized(false) +{ + setPath(path.isEmpty() ? QString::fromLatin1(".") : path); + + bool empty = nameFilters.isEmpty(); + if (!empty) { + empty = true; + for (int i = 0; i < nameFilters.size(); ++i) { + if (!nameFilters.at(i).isEmpty()) { + empty = false; + break; + } + } + } + if (empty) + nameFilters = QStringList(QString::fromLatin1("*")); +} + +QDirPrivate::QDirPrivate(const QDirPrivate ©) + : QSharedData(copy) + , nameFilters(copy.nameFilters) + , sort(copy.sort) + , filters(copy.filters) +#ifdef QT3_SUPPORT + , filterSepChar(copy.filterSepChar) + , matchAllDirs(copy.matchAllDirs) +#endif + , fileListsInitialized(false) + , dirEntry(copy.dirEntry) + , metaData(copy.metaData) +{ +} + +bool QDirPrivate::exists() const +{ + if (fileEngine.isNull()) { + QFileSystemEngine::fillMetaData(dirEntry, metaData, + QFileSystemMetaData::ExistsAttribute | QFileSystemMetaData::DirectoryType); // always stat + return metaData.exists() && metaData.isDirectory(); + } + const QAbstractFileEngine::FileFlags info = + fileEngine->fileFlags(QAbstractFileEngine::DirectoryType + | QAbstractFileEngine::ExistsFlag + | QAbstractFileEngine::Refresh); + if (!(info & QAbstractFileEngine::DirectoryType)) + return false; + return info & QAbstractFileEngine::ExistsFlag; +} + +// static +inline QChar QDirPrivate::getFilterSepChar(const QString &nameFilter) +{ + QChar sep(QLatin1Char(';')); + int i = nameFilter.indexOf(sep, 0); + if (i == -1 && nameFilter.indexOf(QLatin1Char(' '), 0) != -1) + sep = QChar(QLatin1Char(' ')); + return sep; +} + +// static +inline QStringList QDirPrivate::splitFilters(const QString &nameFilter, QChar sep) +{ + if (sep == 0) + sep = getFilterSepChar(nameFilter); + QStringList ret = nameFilter.split(sep); + for (int i = 0; i < ret.count(); ++i) + ret[i] = ret[i].trimmed(); + return ret; +} + +inline void QDirPrivate::setPath(const QString &path) +{ + QString p = QDir::fromNativeSeparators(path); + if (p.endsWith(QLatin1Char('/')) + && p.length() > 1 +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + && (!(p.length() == 3 && p.at(1).unicode() == ':' && p.at(0).isLetter())) +#endif + ) { + p.truncate(p.length() - 1); + } + + dirEntry = QFileSystemEntry(p, QFileSystemEntry::FromInternalPath()); + metaData.clear(); + initFileEngine(); + clearFileLists(); + absoluteDirEntry = QFileSystemEntry(); +} + +inline void QDirPrivate::clearFileLists() +{ + fileListsInitialized = false; + files.clear(); + fileInfos.clear(); +} + +inline void QDirPrivate::resolveAbsoluteEntry() const +{ + if (!absoluteDirEntry.isEmpty() || dirEntry.isEmpty()) + return; + + QString absoluteName; + if (fileEngine.isNull()) { + if (!dirEntry.isRelative()) { + absoluteDirEntry = dirEntry; + return; + } + + absoluteName = QFileSystemEngine::absoluteName(dirEntry).filePath(); + } else { + absoluteName = fileEngine->fileName(QAbstractFileEngine::AbsoluteName); + } + + absoluteDirEntry = QFileSystemEntry(QDir::cleanPath(absoluteName), QFileSystemEntry::FromInternalPath()); +} + +/* For sorting */ +struct QDirSortItem +{ + mutable QString filename_cache; + mutable QString suffix_cache; + QFileInfo item; +}; + + +class QDirSortItemComparator +{ + int qt_cmp_si_sort_flags; +public: + QDirSortItemComparator(int flags) : qt_cmp_si_sort_flags(flags) {} + bool operator()(const QDirSortItem &, const QDirSortItem &); +}; + +bool QDirSortItemComparator::operator()(const QDirSortItem &n1, const QDirSortItem &n2) +{ + const QDirSortItem* f1 = &n1; + const QDirSortItem* f2 = &n2; + + if ((qt_cmp_si_sort_flags & QDir::DirsFirst) && (f1->item.isDir() != f2->item.isDir())) + return f1->item.isDir(); + if ((qt_cmp_si_sort_flags & QDir::DirsLast) && (f1->item.isDir() != f2->item.isDir())) + return !f1->item.isDir(); + + int r = 0; + int sortBy = (qt_cmp_si_sort_flags & QDir::SortByMask) + | (qt_cmp_si_sort_flags & QDir::Type); + + switch (sortBy) { + case QDir::Time: + r = f1->item.lastModified().secsTo(f2->item.lastModified()); + break; + case QDir::Size: + r = int(qBound<qint64>(-1, f2->item.size() - f1->item.size(), 1)); + break; + case QDir::Type: + { + bool ic = qt_cmp_si_sort_flags & QDir::IgnoreCase; + + if (f1->suffix_cache.isNull()) + f1->suffix_cache = ic ? f1->item.suffix().toLower() + : f1->item.suffix(); + if (f2->suffix_cache.isNull()) + f2->suffix_cache = ic ? f2->item.suffix().toLower() + : f2->item.suffix(); + + r = qt_cmp_si_sort_flags & QDir::LocaleAware + ? f1->suffix_cache.localeAwareCompare(f2->suffix_cache) + : f1->suffix_cache.compare(f2->suffix_cache); + } + break; + default: + ; + } + + if (r == 0 && sortBy != QDir::Unsorted) { + // Still not sorted - sort by name + bool ic = qt_cmp_si_sort_flags & QDir::IgnoreCase; + + if (f1->filename_cache.isNull()) + f1->filename_cache = ic ? f1->item.fileName().toLower() + : f1->item.fileName(); + if (f2->filename_cache.isNull()) + f2->filename_cache = ic ? f2->item.fileName().toLower() + : f2->item.fileName(); + + r = qt_cmp_si_sort_flags & QDir::LocaleAware + ? f1->filename_cache.localeAwareCompare(f2->filename_cache) + : f1->filename_cache.compare(f2->filename_cache); + } + if (r == 0) // Enforce an order - the order the items appear in the array + r = (&n1) - (&n2); + if (qt_cmp_si_sort_flags & QDir::Reversed) + return r > 0; + return r < 0; +} + +inline void QDirPrivate::sortFileList(QDir::SortFlags sort, QFileInfoList &l, + QStringList *names, QFileInfoList *infos) +{ + // names and infos are always empty lists or 0 here + int n = l.size(); + if (n > 0) { + if (n == 1 || (sort & QDir::SortByMask) == QDir::Unsorted) { + if (infos) + *infos = l; + if (names) { + for (int i = 0; i < n; ++i) + names->append(l.at(i).fileName()); + } + } else { + QScopedArrayPointer<QDirSortItem> si(new QDirSortItem[n]); + for (int i = 0; i < n; ++i) + si[i].item = l.at(i); + qSort(si.data(), si.data() + n, QDirSortItemComparator(sort)); + // put them back in the list(s) + if (infos) { + for (int i = 0; i < n; ++i) + infos->append(si[i].item); + } + if (names) { + for (int i = 0; i < n; ++i) + names->append(si[i].item.fileName()); + } + } + } +} +inline void QDirPrivate::initFileLists(const QDir &dir) const +{ + if (!fileListsInitialized) { + QFileInfoList l; + QDirIterator it(dir); + while (it.hasNext()) { + it.next(); + l.append(it.fileInfo()); + } + sortFileList(sort, l, &files, &fileInfos); + fileListsInitialized = true; + } +} + +inline void QDirPrivate::initFileEngine() +{ + fileEngine.reset(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(dirEntry, metaData)); +} + +/*! + \class QDir + \brief The QDir class provides access to directory structures and their contents. + + \ingroup io + \ingroup shared + \reentrant + + + A QDir is used to manipulate path names, access information + regarding paths and files, and manipulate the underlying file + system. It can also be used to access Qt's \l{resource system}. + + Qt uses "/" as a universal directory separator in the same way + that "/" is used as a path separator in URLs. If you always use + "/" as a directory separator, Qt will translate your paths to + conform to the underlying operating system. + + A QDir can point to a file using either a relative or an absolute + path. Absolute paths begin with the directory separator + (optionally preceded by a drive specification under Windows). + Relative file names begin with a directory name or a file name and + specify a path relative to the current directory. + + Examples of absolute paths: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 0 + + On Windows, the second example above will be translated to + \c{C:\Documents and Settings} when used to access files. + + Examples of relative paths: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 1 + + You can use the isRelative() or isAbsolute() functions to check if + a QDir is using a relative or an absolute file path. Call + makeAbsolute() to convert a relative QDir to an absolute one. + + \section1 Navigation and Directory Operations + + A directory's path can be obtained with the path() function, and + a new path set with the setPath() function. The absolute path to + a directory is found by calling absolutePath(). + + The name of a directory is found using the dirName() function. This + typically returns the last element in the absolute path that specifies + the location of the directory. However, it can also return "." if + the QDir represents the current directory. + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 2 + + The path for a directory can also be changed with the cd() and cdUp() + functions, both of which operate like familiar shell commands. + When cd() is called with the name of an existing directory, the QDir + object changes directory so that it represents that directory instead. + The cdUp() function changes the directory of the QDir object so that + it refers to its parent directory; i.e. cd("..") is equivalent to + cdUp(). + + Directories can be created with mkdir(), renamed with rename(), and + removed with rmdir(). + + You can test for the presence of a directory with a given name by + using exists(), and the properties of a directory can be tested with + isReadable(), isAbsolute(), isRelative(), and isRoot(). + + The refresh() function re-reads the directory's data from disk. + + \section1 Files and Directory Contents + + Directories contain a number of entries, representing files, + directories, and symbolic links. The number of entries in a + directory is returned by count(). + A string list of the names of all the entries in a directory can be + obtained with entryList(). If you need information about each + entry, use entryInfoList() to obtain a list of QFileInfo objects. + + Paths to files and directories within a directory can be + constructed using filePath() and absoluteFilePath(). + The filePath() function returns a path to the specified file + or directory relative to the path of the QDir object; + absoluteFilePath() returns an absolute path to the specified + file or directory. Neither of these functions checks for the + existence of files or directory; they only construct paths. + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 3 + + Files can be removed by using the remove() function. Directories + cannot be removed in the same way as files; use rmdir() to remove + them instead. + + It is possible to reduce the number of entries returned by + entryList() and entryInfoList() by applying filters to a QDir object. + You can apply a name filter to specify a pattern with wildcards that + file names need to match, an attribute filter that selects properties + of entries and can distinguish between files and directories, and a + sort order. + + Name filters are lists of strings that are passed to setNameFilters(). + Attribute filters consist of a bitwise OR combination of Filters, and + these are specified when calling setFilter(). + The sort order is specified using setSorting() with a bitwise OR + combination of SortFlags. + + You can test to see if a filename matches a filter using the match() + function. + + Filter and sort order flags may also be specified when calling + entryList() and entryInfoList() in order to override previously defined + behavior. + + \section1 The Current Directory and Other Special Paths + + Access to some common directories is provided with a number of static + functions that return QDir objects. There are also corresponding functions + for these that return strings: + + \table + \header \o QDir \o QString \o Return Value + \row \o current() \o currentPath() \o The application's working directory + \row \o home() \o homePath() \o The user's home directory + \row \o root() \o rootPath() \o The root directory + \row \o temp() \o tempPath() \o The system's temporary directory + \endtable + + The setCurrent() static function can also be used to set the application's + working directory. + + If you want to find the directory containing the application's executable, + see \l{QCoreApplication::applicationDirPath()}. + + The drives() static function provides a list of root directories for each + device that contains a filing system. On Unix systems this returns a list + containing a single root directory "/"; on Windows the list will usually + contain \c{C:/}, and possibly other drive letters such as \c{D:/}, depending + on the configuration of the user's system. + + \section1 Path Manipulation and Strings + + Paths containing "." elements that reference the current directory at that + point in the path, ".." elements that reference the parent directory, and + symbolic links can be reduced to a canonical form using the canonicalPath() + function. + + Paths can also be simplified by using cleanPath() to remove redundant "/" + and ".." elements. + + It is sometimes necessary to be able to show a path in the native + representation for the user's platform. The static toNativeSeparators() + function returns a copy of the specified path in which each directory + separator is replaced by the appropriate separator for the underlying + operating system. + + \section1 Examples + + Check if a directory exists: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 4 + + (We could also use the static convenience function + QFile::exists().) + + Traversing directories and reading a file: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 5 + + A program that lists all the files in the current directory + (excluding symbolic links), sorted by size, smallest first: + + \snippet doc/src/snippets/qdir-listfiles/main.cpp 0 + + \sa QFileInfo, QFile, QFileDialog, QApplication::applicationDirPath(), {Find Files Example} +*/ + +/*! + Constructs a QDir pointing to the given directory \a path. If path + is empty the program's working directory, ("."), is used. + + \sa currentPath() +*/ +QDir::QDir(const QString &path) : d_ptr(new QDirPrivate(path)) +{ +} + +/*! + Constructs a QDir with path \a path, that filters its entries by + name using \a nameFilter and by attributes using \a filters. It + also sorts the names using \a sort. + + The default \a nameFilter is an empty string, which excludes + nothing; the default \a filters is \l AllEntries, which also means + exclude nothing. The default \a sort is \l Name | \l IgnoreCase, + i.e. sort by name case-insensitively. + + If \a path is an empty string, QDir uses "." (the current + directory). If \a nameFilter is an empty string, QDir uses the + name filter "*" (all files). + + Note that \a path need not exist. + + \sa exists(), setPath(), setNameFilter(), setFilter(), setSorting() +*/ +QDir::QDir(const QString &path, const QString &nameFilter, + SortFlags sort, Filters filters) + : d_ptr(new QDirPrivate(path, QDir::nameFiltersFromString(nameFilter), sort, filters)) +{ +} + +/*! + Constructs a QDir object that is a copy of the QDir object for + directory \a dir. + + \sa operator=() +*/ +QDir::QDir(const QDir &dir) + : d_ptr(dir.d_ptr) +{ +} + +/*! + Destroys the QDir object frees up its resources. This has no + effect on the underlying directory in the file system. +*/ +QDir::~QDir() +{ +} + +/*! + Sets the path of the directory to \a path. The path is cleaned of + redundant ".", ".." and of multiple separators. No check is made + to see whether a directory with this path actually exists; but you + can check for yourself using exists(). + + The path can be either absolute or relative. Absolute paths begin + with the directory separator "/" (optionally preceded by a drive + specification under Windows). Relative file names begin with a + directory name or a file name and specify a path relative to the + current directory. An example of an absolute path is the string + "/tmp/quartz", a relative path might look like "src/fatlib". + + \sa path(), absolutePath(), exists(), cleanPath(), dirName(), + absoluteFilePath(), isRelative(), makeAbsolute() +*/ +void QDir::setPath(const QString &path) +{ + d_ptr->setPath(path); +} + +/*! + Returns the path. This may contain symbolic links, but never + contains redundant ".", ".." or multiple separators. + + The returned path can be either absolute or relative (see + setPath()). + + \sa setPath(), absolutePath(), exists(), cleanPath(), dirName(), + absoluteFilePath(), toNativeSeparators(), makeAbsolute() +*/ +QString QDir::path() const +{ + const QDirPrivate* d = d_ptr.constData(); + return d->dirEntry.filePath(); +} + +/*! + Returns the absolute path (a path that starts with "/" or with a + drive specification), which may contain symbolic links, but never + contains redundant ".", ".." or multiple separators. + + \sa setPath(), canonicalPath(), exists(), cleanPath(), + dirName(), absoluteFilePath() +*/ +QString QDir::absolutePath() const +{ + const QDirPrivate* d = d_ptr.constData(); + d->resolveAbsoluteEntry(); + return d->absoluteDirEntry.filePath(); +} + +/*! + Returns the canonical path, i.e. a path without symbolic links or + redundant "." or ".." elements. + + On systems that do not have symbolic links this function will + always return the same string that absolutePath() returns. If the + canonical path does not exist (normally due to dangling symbolic + links) canonicalPath() returns an empty string. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 6 + + \sa path(), absolutePath(), exists(), cleanPath(), dirName(), + absoluteFilePath() +*/ +QString QDir::canonicalPath() const +{ + const QDirPrivate* d = d_ptr.constData(); + if (d->fileEngine.isNull()) { + QFileSystemEntry answer = QFileSystemEngine::canonicalName(d->dirEntry, d->metaData); + return answer.filePath(); + } + return d->fileEngine->fileName(QAbstractFileEngine::CanonicalName); +} + +/*! + Returns the name of the directory; this is \e not the same as the + path, e.g. a directory with the name "mail", might have the path + "/var/spool/mail". If the directory has no name (e.g. it is the + root directory) an empty string is returned. + + No check is made to ensure that a directory with this name + actually exists; but see exists(). + + \sa path(), filePath(), absolutePath(), absoluteFilePath() +*/ +QString QDir::dirName() const +{ + const QDirPrivate* d = d_ptr.constData(); + return d->dirEntry.fileName(); +} + +/*! + Returns the path name of a file in the directory. Does \e not + check if the file actually exists in the directory; but see + exists(). If the QDir is relative the returned path name will also + be relative. Redundant multiple separators or "." and ".." + directories in \a fileName are not removed (see cleanPath()). + + \sa dirName() absoluteFilePath(), isRelative(), canonicalPath() +*/ +QString QDir::filePath(const QString &fileName) const +{ + const QDirPrivate* d = d_ptr.constData(); + if (isAbsolutePath(fileName)) + return QString(fileName); + + QString ret = d->dirEntry.filePath(); + if (!fileName.isEmpty()) { + if (!ret.isEmpty() && ret[(int)ret.length()-1] != QLatin1Char('/') && fileName[0] != QLatin1Char('/')) + ret += QLatin1Char('/'); + ret += fileName; + } + return ret; +} + +/*! + Returns the absolute path name of a file in the directory. Does \e + not check if the file actually exists in the directory; but see + exists(). Redundant multiple separators or "." and ".." + directories in \a fileName are not removed (see cleanPath()). + + \sa relativeFilePath() filePath() canonicalPath() +*/ +QString QDir::absoluteFilePath(const QString &fileName) const +{ + const QDirPrivate* d = d_ptr.constData(); + if (isAbsolutePath(fileName)) + return fileName; + + d->resolveAbsoluteEntry(); + if (fileName.isEmpty()) + return d->absoluteDirEntry.filePath(); + if (!d->absoluteDirEntry.isRoot()) + return d->absoluteDirEntry.filePath() % QLatin1Char('/') % fileName; + return d->absoluteDirEntry.filePath() % fileName; +} + +/*! + Returns the path to \a fileName relative to the directory. + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 7 + + \sa absoluteFilePath() filePath() canonicalPath() +*/ +QString QDir::relativeFilePath(const QString &fileName) const +{ + QString dir = cleanPath(absolutePath()); + QString file = cleanPath(fileName); + + if (isRelativePath(file) || isRelativePath(dir)) + return file; + + QString dirDrive = driveSpec(dir); + QString fileDrive = driveSpec(file); + + bool fileDriveMissing = false; + if (fileDrive.isEmpty()) { + fileDrive = dirDrive; + fileDriveMissing = true; + } + +#ifdef Q_OS_WIN + if (fileDrive.toLower() != dirDrive.toLower() + || (file.startsWith(QLatin1String("//")) + && !dir.startsWith(QLatin1String("//")))) +#elif defined(Q_OS_SYMBIAN) + if (fileDrive.toLower() != dirDrive.toLower()) +#else + if (fileDrive != dirDrive) +#endif + return file; + + dir.remove(0, dirDrive.size()); + if (!fileDriveMissing) + file.remove(0, fileDrive.size()); + + QString result; + QStringList dirElts = dir.split(QLatin1Char('/'), QString::SkipEmptyParts); + QStringList fileElts = file.split(QLatin1Char('/'), QString::SkipEmptyParts); + + int i = 0; + while (i < dirElts.size() && i < fileElts.size() && +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + dirElts.at(i).toLower() == fileElts.at(i).toLower()) +#else + dirElts.at(i) == fileElts.at(i)) +#endif + ++i; + + for (int j = 0; j < dirElts.size() - i; ++j) + result += QLatin1String("../"); + + for (int j = i; j < fileElts.size(); ++j) { + result += fileElts.at(j); + if (j < fileElts.size() - 1) + result += QLatin1Char('/'); + } + + return result; +} + +#ifndef QT_NO_DEPRECATED +/*! + \obsolete + + Use QDir::toNativeSeparators() instead. +*/ +QString QDir::convertSeparators(const QString &pathName) +{ + return toNativeSeparators(pathName); +} +#endif + +/*! + \since 4.2 + + Returns \a pathName with the '/' separators converted to + separators that are appropriate for the underlying operating + system. + + On Windows, toNativeSeparators("c:/winnt/system32") returns + "c:\\winnt\\system32". + + The returned string may be the same as the argument on some + operating systems, for example on Unix. + + \sa fromNativeSeparators(), separator() +*/ +QString QDir::toNativeSeparators(const QString &pathName) +{ + QString n(pathName); +#if defined(Q_FS_FAT) || defined(Q_OS_OS2EMX) || defined(Q_OS_SYMBIAN) + for (int i = 0; i < (int)n.length(); ++i) { + if (n[i] == QLatin1Char('/')) + n[i] = QLatin1Char('\\'); + } +#endif + return n; +} + +/*! + \since 4.2 + + Returns \a pathName using '/' as file separator. On Windows, + for instance, fromNativeSeparators("\c{c:\\winnt\\system32}") returns + "c:/winnt/system32". + + The returned string may be the same as the argument on some + operating systems, for example on Unix. + + \sa toNativeSeparators(), separator() +*/ +QString QDir::fromNativeSeparators(const QString &pathName) +{ + QString n(pathName); +#if defined(Q_FS_FAT) || defined(Q_OS_OS2EMX) || defined(Q_OS_SYMBIAN) + for (int i = 0; i < (int)n.length(); ++i) { + if (n[i] == QLatin1Char('\\')) + n[i] = QLatin1Char('/'); + } +#endif + return n; +} + +/*! + Changes the QDir's directory to \a dirName. + + Returns true if the new directory exists and is readable; + otherwise returns false. Note that the logical cd() operation is + not performed if the new directory does not exist. + + Calling cd("..") is equivalent to calling cdUp(). + + \sa cdUp(), isReadable(), exists(), path() +*/ +bool QDir::cd(const QString &dirName) +{ + // Don't detach just yet. + const QDirPrivate * const d = d_ptr.constData(); + + if (dirName.isEmpty() || dirName == QLatin1String(".")) + return true; + QString newPath; + if (isAbsolutePath(dirName)) { + newPath = cleanPath(dirName); + } else { + if (isRoot()) { + if (dirName == QLatin1String("..")) + return false; + newPath = d->dirEntry.filePath(); + } else { + newPath = d->dirEntry.filePath() % QLatin1Char('/'); + } + + newPath += dirName; + if (dirName.indexOf(QLatin1Char('/')) >= 0 + || dirName == QLatin1String("..") + || d->dirEntry.filePath() == QLatin1String(".")) { + newPath = cleanPath(newPath); + /* + If newPath starts with .., we convert it to absolute to + avoid infinite looping on + + QDir dir("."); + while (dir.cdUp()) + ; + */ + if (newPath.startsWith(QLatin1String(".."))) { + newPath = QFileInfo(newPath).absoluteFilePath(); + } + } + } + + QScopedPointer<QDirPrivate> dir(new QDirPrivate(*d_ptr.constData())); + dir->setPath(newPath); + if (!dir->exists()) + return false; + + d_ptr = dir.take(); + return true; +} + +/*! + Changes directory by moving one directory up from the QDir's + current directory. + + Returns true if the new directory exists and is readable; + otherwise returns false. Note that the logical cdUp() operation is + not performed if the new directory does not exist. + + \sa cd(), isReadable(), exists(), path() +*/ +bool QDir::cdUp() +{ + return cd(QString::fromLatin1("..")); +} + +/*! + Returns the string list set by setNameFilters() +*/ +QStringList QDir::nameFilters() const +{ + const QDirPrivate* d = d_ptr.constData(); + return d->nameFilters; +} + +/*! + Sets the name filters used by entryList() and entryInfoList() to the + list of filters specified by \a nameFilters. + + Each name filter is a wildcard (globbing) filter that understands + \c{*} and \c{?} wildcards. (See \l{QRegExp wildcard matching}.) + + For example, the following code sets three name filters on a QDir + to ensure that only files with extensions typically used for C++ + source files are listed: + + \snippet doc/src/snippets/qdir-namefilters/main.cpp 0 + + \sa nameFilters(), setFilter() +*/ +void QDir::setNameFilters(const QStringList &nameFilters) +{ + QDirPrivate* d = d_ptr.data(); + d->initFileEngine(); + d->clearFileLists(); + + d->nameFilters = nameFilters; +} + +/*! + \obsolete + + Use QDir::addSearchPath() with a prefix instead. + + Adds \a path to the search paths searched in to find resources + that are not specified with an absolute path. The default search + path is to search only in the root (\c{:/}). + + \sa {The Qt Resource System} +*/ +void QDir::addResourceSearchPath(const QString &path) +{ +#ifdef QT_BUILD_CORE_LIB + QResource::addSearchPath(path); +#else + Q_UNUSED(path) +#endif +} + +#ifdef QT_BUILD_CORE_LIB +/*! + \since 4.3 + + Sets or replaces Qt's search paths for file names with the prefix \a prefix + to \a searchPaths. + + To specify a prefix for a file name, prepend the prefix followed by a single + colon (e.g., "images:undo.png", "xmldocs:books.xml"). \a prefix can only + contain letters or numbers (e.g., it cannot contain a colon, nor a slash). + + Qt uses this search path to locate files with a known prefix. The search + path entries are tested in order, starting with the first entry. + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 8 + + File name prefix must be at least 2 characters long to avoid conflicts with + Windows drive letters. + + Search paths may contain paths to \l{The Qt Resource System}. +*/ +void QDir::setSearchPaths(const QString &prefix, const QStringList &searchPaths) +{ + if (prefix.length() < 2) { + qWarning("QDir::setSearchPaths: Prefix must be longer than 1 character"); + return; + } + + for (int i = 0; i < prefix.count(); ++i) { + if (!prefix.at(i).isLetterOrNumber()) { + qWarning("QDir::setSearchPaths: Prefix can only contain letters or numbers"); + return; + } + } + + QWriteLocker lock(&QCoreGlobalData::instance()->dirSearchPathsLock); + QMap<QString, QStringList> &paths = QCoreGlobalData::instance()->dirSearchPaths; + if (searchPaths.isEmpty()) { + paths.remove(prefix); + } else { + paths.insert(prefix, searchPaths); + } +} + +/*! + \since 4.3 + + Adds \a path to the search path for \a prefix. + + \sa setSearchPaths() +*/ +void QDir::addSearchPath(const QString &prefix, const QString &path) +{ + if (path.isEmpty()) + return; + + QWriteLocker lock(&QCoreGlobalData::instance()->dirSearchPathsLock); + QCoreGlobalData::instance()->dirSearchPaths[prefix] += path; +} + +/*! + \since 4.3 + + Returns the search paths for \a prefix. + + \sa setSearchPaths(), addSearchPath() +*/ +QStringList QDir::searchPaths(const QString &prefix) +{ + QReadLocker lock(&QCoreGlobalData::instance()->dirSearchPathsLock); + return QCoreGlobalData::instance()->dirSearchPaths.value(prefix); +} + +#endif // QT_BUILD_CORE_LIB + +/*! + Returns the value set by setFilter() +*/ +QDir::Filters QDir::filter() const +{ + const QDirPrivate* d = d_ptr.constData(); + return d->filters; +} + +/*! + \enum QDir::Filter + + This enum describes the filtering options available to QDir; e.g. + for entryList() and entryInfoList(). The filter value is specified + by combining values from the following list using the bitwise OR + operator: + + \value Dirs List directories that match the filters. + \value AllDirs List all directories; i.e. don't apply the filters + to directory names. + \value Files List files. + \value Drives List disk drives (ignored under Unix). + \value NoSymLinks Do not list symbolic links (ignored by operating + systems that don't support symbolic links). + \value NoDotAndDotDot Do not list the special entries "." and "..". + \value NoDot Do not list the special entry ".". + \value NoDotDot Do not list the special entry "..". + \value AllEntries List directories, files, drives and symlinks (this does not list + broken symlinks unless you specify System). + \value Readable List files for which the application has read + access. The Readable value needs to be combined + with Dirs or Files. + \value Writable List files for which the application has write + access. The Writable value needs to be combined + with Dirs or Files. + \value Executable List files for which the application has + execute access. The Executable value needs to be + combined with Dirs or Files. + \value Modified Only list files that have been modified (ignored + on Unix). + \value Hidden List hidden files (on Unix, files starting with a "."). + \value System List system files (on Unix, FIFOs, sockets and + device files are included; on Windows, \c {.lnk} + files are included) + \value CaseSensitive The filter should be case sensitive. + + \omitvalue DefaultFilter + \omitvalue TypeMask + \omitvalue All + \omitvalue RWEMask + \omitvalue AccessMask + \omitvalue PermissionMask + \omitvalue NoFilter + + Functions that use Filter enum values to filter lists of files + and directories will include symbolic links to files and directories + unless you set the NoSymLinks value. + + A default constructed QDir will not filter out files based on + their permissions, so entryList() and entryInfoList() will return + all files that are readable, writable, executable, or any + combination of the three. This makes the default easy to write, + and at the same time useful. + + For example, setting the \c Readable, \c Writable, and \c Files + flags allows all files to be listed for which the application has read + access, write access or both. If the \c Dirs and \c Drives flags are + also included in this combination then all drives, directories, all + files that the application can read, write, or execute, and symlinks + to such files/directories can be listed. + + To retrieve the permissons for a directory, use the + entryInfoList() function to get the associated QFileInfo objects + and then use the QFileInfo::permissons() to obtain the permissions + and ownership for each file. +*/ + +/*! + Sets the filter used by entryList() and entryInfoList() to \a + filters. The filter is used to specify the kind of files that + should be returned by entryList() and entryInfoList(). See + \l{QDir::Filter}. + + \sa filter(), setNameFilters() +*/ +void QDir::setFilter(Filters filters) +{ + QDirPrivate* d = d_ptr.data(); + d->initFileEngine(); + d->clearFileLists(); + + d->filters = filters; +} + +/*! + Returns the value set by setSorting() + + \sa setSorting() SortFlag +*/ +QDir::SortFlags QDir::sorting() const +{ + const QDirPrivate* d = d_ptr.constData(); + return d->sort; +} + +/*! + \enum QDir::SortFlag + + This enum describes the sort options available to QDir, e.g. for + entryList() and entryInfoList(). The sort value is specified by + OR-ing together values from the following list: + + \value Name Sort by name. + \value Time Sort by time (modification time). + \value Size Sort by file size. + \value Type Sort by file type (extension). + \value Unsorted Do not sort. + \value NoSort Not sorted by default. + + \value DirsFirst Put the directories first, then the files. + \value DirsLast Put the files first, then the directories. + \value Reversed Reverse the sort order. + \value IgnoreCase Sort case-insensitively. + \value LocaleAware Sort items appropriately using the current locale settings. + + \omitvalue SortByMask + \omitvalue DefaultSort + + You can only specify one of the first four. + + If you specify both DirsFirst and Reversed, directories are + still put first, but in reverse order; the files will be listed + after the directories, again in reverse order. +*/ + +/*! + Sets the sort order used by entryList() and entryInfoList(). + + The \a sort is specified by OR-ing values from the enum + \l{QDir::SortFlag}. + + \sa sorting() SortFlag +*/ +void QDir::setSorting(SortFlags sort) +{ + QDirPrivate* d = d_ptr.data(); + d->initFileEngine(); + d->clearFileLists(); + + d->sort = sort; +} + +/*! + Returns the total number of directories and files in the directory. + + Equivalent to entryList().count(). + + \sa operator[](), entryList() +*/ +uint QDir::count() const +{ + const QDirPrivate* d = d_ptr.constData(); + d->initFileLists(*this); + return d->files.count(); +} + +/*! + Returns the file name at position \a pos in the list of file + names. Equivalent to entryList().at(index). + \a pos must be a valid index position in the list (i.e., 0 <= pos < count()). + + \sa count(), entryList() +*/ +QString QDir::operator[](int pos) const +{ + const QDirPrivate* d = d_ptr.constData(); + d->initFileLists(*this); + return d->files[pos]; +} + +/*! + \overload + + Returns a list of the names of all the files and directories in + the directory, ordered according to the name and attribute filters + previously set with setNameFilters() and setFilter(), and sorted according + to the flags set with setSorting(). + + The attribute filter and sorting specifications can be overridden using the + \a filters and \a sort arguments. + + Returns an empty list if the directory is unreadable, does not + exist, or if nothing matches the specification. + + \note To list symlinks that point to non existing files, \l System must be + passed to the filter. + + \sa entryInfoList(), setNameFilters(), setSorting(), setFilter() +*/ +QStringList QDir::entryList(Filters filters, SortFlags sort) const +{ + const QDirPrivate* d = d_ptr.constData(); + return entryList(d->nameFilters, filters, sort); +} + + +/*! + \overload + + Returns a list of QFileInfo objects for all the files and directories in + the directory, ordered according to the name and attribute filters + previously set with setNameFilters() and setFilter(), and sorted according + to the flags set with setSorting(). + + The attribute filter and sorting specifications can be overridden using the + \a filters and \a sort arguments. + + Returns an empty list if the directory is unreadable, does not + exist, or if nothing matches the specification. + + \sa entryList(), setNameFilters(), setSorting(), setFilter(), isReadable(), exists() +*/ +QFileInfoList QDir::entryInfoList(Filters filters, SortFlags sort) const +{ + const QDirPrivate* d = d_ptr.constData(); + return entryInfoList(d->nameFilters, filters, sort); +} + +/*! + Returns a list of the names of all the files and + directories in the directory, ordered according to the name + and attribute filters previously set with setNameFilters() + and setFilter(), and sorted according to the flags set with + setSorting(). + + The name filter, file attribute filter, and sorting specification + can be overridden using the \a nameFilters, \a filters, and \a sort + arguments. + + Returns an empty list if the directory is unreadable, does not + exist, or if nothing matches the specification. + + \sa entryInfoList(), setNameFilters(), setSorting(), setFilter() +*/ +QStringList QDir::entryList(const QStringList &nameFilters, Filters filters, + SortFlags sort) const +{ + const QDirPrivate* d = d_ptr.constData(); + + if (filters == NoFilter) + filters = d->filters; +#ifdef QT3_SUPPORT + if (d->matchAllDirs) + filters |= AllDirs; +#endif + if (sort == NoSort) + sort = d->sort; + + if (filters == d->filters && sort == d->sort && nameFilters == d->nameFilters) { + d->initFileLists(*this); + return d->files; + } + + QFileInfoList l; + QDirIterator it(d->dirEntry.filePath(), nameFilters, filters); + while (it.hasNext()) { + it.next(); + l.append(it.fileInfo()); + } + QStringList ret; + d->sortFileList(sort, l, &ret, 0); + return ret; +} + +/*! + Returns a list of QFileInfo objects for all the files and + directories in the directory, ordered according to the name + and attribute filters previously set with setNameFilters() + and setFilter(), and sorted according to the flags set with + setSorting(). + + The name filter, file attribute filter, and sorting specification + can be overridden using the \a nameFilters, \a filters, and \a sort + arguments. + + Returns an empty list if the directory is unreadable, does not + exist, or if nothing matches the specification. + + \sa entryList(), setNameFilters(), setSorting(), setFilter(), isReadable(), exists() +*/ +QFileInfoList QDir::entryInfoList(const QStringList &nameFilters, Filters filters, + SortFlags sort) const +{ + const QDirPrivate* d = d_ptr.constData(); + + if (filters == NoFilter) + filters = d->filters; +#ifdef QT3_SUPPORT + if (d->matchAllDirs) + filters |= AllDirs; +#endif + if (sort == NoSort) + sort = d->sort; + + if (filters == d->filters && sort == d->sort && nameFilters == d->nameFilters) { + d->initFileLists(*this); + return d->fileInfos; + } + + QFileInfoList l; + QDirIterator it(d->dirEntry.filePath(), nameFilters, filters); + while (it.hasNext()) { + it.next(); + l.append(it.fileInfo()); + } + QFileInfoList ret; + d->sortFileList(sort, l, 0, &ret); + return ret; +} + +/*! + Creates a sub-directory called \a dirName. + + Returns true on success; otherwise returns false. + + If the directory already exists when this function is called, it will return false. + + \sa rmdir() +*/ +// ### Qt5: behaviour when directory already exists should be made consistent for mkdir and mkpath +bool QDir::mkdir(const QString &dirName) const +{ + const QDirPrivate* d = d_ptr.constData(); + + if (dirName.isEmpty()) { + qWarning("QDir::mkdir: Empty or null file name(s)"); + return false; + } + + QString fn = filePath(dirName); + if (d->fileEngine.isNull()) + return QFileSystemEngine::createDirectory(QFileSystemEntry(fn), false); + return d->fileEngine->mkdir(fn, false); +} + +/*! + Removes the directory specified by \a dirName. + + The directory must be empty for rmdir() to succeed. + + Returns true if successful; otherwise returns false. + + \sa mkdir() +*/ +bool QDir::rmdir(const QString &dirName) const +{ + const QDirPrivate* d = d_ptr.constData(); + + if (dirName.isEmpty()) { + qWarning("QDir::rmdir: Empty or null file name(s)"); + return false; + } + + QString fn = filePath(dirName); + if (d->fileEngine.isNull()) + return QFileSystemEngine::removeDirectory(QFileSystemEntry(fn), false); + + return d->fileEngine->rmdir(fn, false); +} + +/*! + Creates the directory path \a dirPath. + + The function will create all parent directories necessary to + create the directory. + + Returns true if successful; otherwise returns false. + + If the path already exists when this function is called, it will return true. + + \sa rmpath() +*/ +// ### Qt5: behaviour when directory already exists should be made consistent for mkdir and mkpath +bool QDir::mkpath(const QString &dirPath) const +{ + const QDirPrivate* d = d_ptr.constData(); + + if (dirPath.isEmpty()) { + qWarning("QDir::mkpath: Empty or null file name(s)"); + return false; + } + + QString fn = filePath(dirPath); + if (d->fileEngine.isNull()) + return QFileSystemEngine::createDirectory(QFileSystemEntry(fn), true); + return d->fileEngine->mkdir(fn, true); +} + +/*! + Removes the directory path \a dirPath. + + The function will remove all parent directories in \a dirPath, + provided that they are empty. This is the opposite of + mkpath(dirPath). + + Returns true if successful; otherwise returns false. + + \sa mkpath() +*/ +bool QDir::rmpath(const QString &dirPath) const +{ + const QDirPrivate* d = d_ptr.constData(); + + if (dirPath.isEmpty()) { + qWarning("QDir::rmpath: Empty or null file name(s)"); + return false; + } + + QString fn = filePath(dirPath); + if (d->fileEngine.isNull()) + return QFileSystemEngine::removeDirectory(QFileSystemEntry(fn), true); + return d->fileEngine->rmdir(fn, true); +} + +/*! + Returns true if the directory is readable \e and we can open files + by name; otherwise returns false. + + \warning A false value from this function is not a guarantee that + files in the directory are not accessible. + + \sa QFileInfo::isReadable() +*/ +bool QDir::isReadable() const +{ + const QDirPrivate* d = d_ptr.constData(); + + if (d->fileEngine.isNull()) { + if (!d->metaData.hasFlags(QFileSystemMetaData::UserReadPermission)) + QFileSystemEngine::fillMetaData(d->dirEntry, d->metaData, QFileSystemMetaData::UserReadPermission); + + return (d->metaData.permissions() & QFile::ReadUser) != 0; + } + + const QAbstractFileEngine::FileFlags info = + d->fileEngine->fileFlags(QAbstractFileEngine::DirectoryType + | QAbstractFileEngine::PermsMask); + if (!(info & QAbstractFileEngine::DirectoryType)) + return false; + return info & QAbstractFileEngine::ReadUserPerm; +} + +/*! + \overload + + Returns true if the directory exists; otherwise returns false. + (If a file with the same name is found this function will return false). + + The overload of this function that accepts an argument is used to test + for the presence of files and directories within a directory. + + \sa QFileInfo::exists(), QFile::exists() +*/ +bool QDir::exists() const +{ + return d_ptr->exists(); +} + +/*! + Returns true if the directory is the root directory; otherwise + returns false. + + Note: If the directory is a symbolic link to the root directory + this function returns false. If you want to test for this use + canonicalPath(), e.g. + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 9 + + \sa root(), rootPath() +*/ +bool QDir::isRoot() const +{ + if (d_ptr->fileEngine.isNull()) + return d_ptr->dirEntry.isRoot(); + return d_ptr->fileEngine->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::RootFlag; +} + +/*! + \fn bool QDir::isAbsolute() const + + Returns true if the directory's path is absolute; otherwise + returns false. See isAbsolutePath(). + + \sa isRelative() makeAbsolute() cleanPath() +*/ + +/*! + \fn bool QDir::isAbsolutePath(const QString &) + + Returns true if \a path is absolute; returns false if it is + relative. + + \sa isAbsolute() isRelativePath() makeAbsolute() cleanPath() +*/ + +/*! + Returns true if the directory path is relative; otherwise returns + false. (Under Unix a path is relative if it does not start with a + "/"). + + \sa makeAbsolute() isAbsolute() isAbsolutePath() cleanPath() +*/ +bool QDir::isRelative() const +{ + if (d_ptr->fileEngine.isNull()) + return d_ptr->dirEntry.isRelative(); + return d_ptr->fileEngine->isRelativePath(); +} + + +/*! + Converts the directory path to an absolute path. If it is already + absolute nothing happens. Returns true if the conversion + succeeded; otherwise returns false. + + \sa isAbsolute() isAbsolutePath() isRelative() cleanPath() +*/ +bool QDir::makeAbsolute() +{ + const QDirPrivate *d = d_ptr.constData(); + QScopedPointer<QDirPrivate> dir; + if (!d->fileEngine.isNull()) { + QString absolutePath = d->fileEngine->fileName(QAbstractFileEngine::AbsoluteName); + if (QDir::isRelativePath(absolutePath)) + return false; + + dir.reset(new QDirPrivate(*d_ptr.constData())); + dir->setPath(absolutePath); + } else { // native FS + d->resolveAbsoluteEntry(); + dir.reset(new QDirPrivate(*d_ptr.constData())); + dir->setPath(d->absoluteDirEntry.filePath()); + } + d_ptr = dir.take(); // actually detach + return true; +} + +/*! + Returns true if directory \a dir and this directory have the same + path and their sort and filter settings are the same; otherwise + returns false. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 10 +*/ +bool QDir::operator==(const QDir &dir) const +{ + const QDirPrivate *d = d_ptr.constData(); + const QDirPrivate *other = dir.d_ptr.constData(); + + if (d == other) + return true; + Qt::CaseSensitivity sensitive; + if (d->fileEngine.isNull() || other->fileEngine.isNull()) { + if (d->fileEngine.data() != other->fileEngine.data()) // one is native, the other is a custom file-engine + return false; + + sensitive = QFileSystemEngine::isCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive; + } else { + if (d->fileEngine->caseSensitive() != other->fileEngine->caseSensitive()) + return false; + sensitive = d->fileEngine->caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive; + } + + if (d->filters == other->filters + && d->sort == other->sort + && d->nameFilters == other->nameFilters) { + d->resolveAbsoluteEntry(); + other->resolveAbsoluteEntry(); + return d->absoluteDirEntry.filePath().compare(other->absoluteDirEntry.filePath(), sensitive) == 0; + } + return false; +} + +/*! + Makes a copy of the \a dir object and assigns it to this QDir + object. +*/ +QDir &QDir::operator=(const QDir &dir) +{ + d_ptr = dir.d_ptr; + return *this; +} + +/*! + \overload + \obsolete + + Sets the directory path to the given \a path. + + Use setPath() instead. +*/ +QDir &QDir::operator=(const QString &path) +{ + d_ptr->setPath(path); + return *this; +} + +/*! + \fn bool QDir::operator!=(const QDir &dir) const + + Returns true if directory \a dir and this directory have different + paths or different sort or filter settings; otherwise returns + false. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 11 +*/ + +/*! + Removes the file, \a fileName. + + Returns true if the file is removed successfully; otherwise + returns false. +*/ +bool QDir::remove(const QString &fileName) +{ + if (fileName.isEmpty()) { + qWarning("QDir::remove: Empty or null file name"); + return false; + } + return QFile::remove(filePath(fileName)); +} + +/*! + Renames a file or directory from \a oldName to \a newName, and returns + true if successful; otherwise returns false. + + On most file systems, rename() fails only if \a oldName does not + exist, if \a newName and \a oldName are not on the same + partition or if a file with the new name already exists. + However, there are also other reasons why rename() can + fail. For example, on at least one file system rename() fails if + \a newName points to an open file. +*/ +bool QDir::rename(const QString &oldName, const QString &newName) +{ + if (oldName.isEmpty() || newName.isEmpty()) { + qWarning("QDir::rename: Empty or null file name(s)"); + return false; + } + + QFile file(filePath(oldName)); + if (!file.exists()) + return false; + return file.rename(filePath(newName)); +} + +/*! + Returns true if the file called \a name exists; otherwise returns + false. + + Unless \a name contains an absolute file path, the file name is assumed + to be relative to the directory itself, so this function is typically used + to check for the presence of files within a directory. + + \sa QFileInfo::exists(), QFile::exists() +*/ +bool QDir::exists(const QString &name) const +{ + if (name.isEmpty()) { + qWarning("QDir::exists: Empty or null file name"); + return false; + } + return QFile::exists(filePath(name)); +} + +/*! + Returns a list of the root directories on this system. + + On Windows this returns a list of QFileInfo objects containing "C:/", + "D:/", etc. On other operating systems, it returns a list containing + just one root directory (i.e. "/"). + + \sa root(), rootPath() +*/ +QFileInfoList QDir::drives() +{ +#ifdef QT_NO_FSFILEENGINE + return QFileInfoList(); +#else + return QFSFileEngine::drives(); +#endif +} + +/*! + Returns the native directory separator: "/" under Unix (including + Mac OS X) and "\\" under Windows. + + You do not need to use this function to build file paths. If you + always use "/", Qt will translate your paths to conform to the + underlying operating system. If you want to display paths to the + user using their operating system's separator use + toNativeSeparators(). +*/ +QChar QDir::separator() +{ +#if defined (Q_FS_FAT) || defined(Q_WS_WIN) || defined(Q_OS_SYMBIAN) + return QLatin1Char('\\'); +#elif defined(Q_OS_UNIX) + return QLatin1Char('/'); +#elif defined (Q_OS_MAC) + return QLatin1Char(':'); +#else + return QLatin1Char('/'); +#endif +} + +/*! + Sets the application's current working directory to \a path. + Returns true if the directory was successfully changed; otherwise + returns false. + + \sa current(), currentPath(), home(), root(), temp() +*/ +bool QDir::setCurrent(const QString &path) +{ + return QFileSystemEngine::setCurrentPath(QFileSystemEntry(path)); +} + +/*! + \fn QDir QDir::current() + + Returns the application's current directory. + + The directory is constructed using the absolute path of the current directory, + ensuring that its path() will be the same as its absolutePath(). + + \sa currentPath(), setCurrent(), home(), root(), temp() +*/ + +/*! + Returns the absolute path of the application's current directory. + + \sa current(), setCurrent(), homePath(), rootPath(), tempPath() +*/ +QString QDir::currentPath() +{ + return QFileSystemEngine::currentPath().filePath(); +} + +/*! + \fn QString QDir::currentDirPath() + Returns the absolute path of the application's current directory. + + Use currentPath() instead. + + \sa currentPath(), setCurrent() +*/ + +/*! + \fn QDir QDir::home() + + Returns the user's home directory. + + The directory is constructed using the absolute path of the home directory, + ensuring that its path() will be the same as its absolutePath(). + + See homePath() for details. + + \sa drives(), current(), root(), temp() +*/ + +/*! + Returns the absolute path of the user's home directory. + + Under Windows this function will return the directory of the + current user's profile. Typically, this is: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 12 + + Use the toNativeSeparators() function to convert the separators to + the ones that are appropriate for the underlying operating system. + + If the directory of the current user's profile does not exist or + cannot be retrieved, the following alternatives will be checked (in + the given order) until an existing and available path is found: + + \list 1 + \o The path specified by the \c USERPROFILE environment variable. + \o The path formed by concatenating the \c HOMEDRIVE and \c HOMEPATH + environment variables. + \o The path specified by the \c HOME environment variable. + \o The path returned by the rootPath() function (which uses the \c SystemDrive + environment variable) + \o The \c{C:/} directory. + \endlist + + Under non-Windows operating systems the \c HOME environment + variable is used if it exists, otherwise the path returned by the + rootPath(). On Symbian always the same as the path returned by the rootPath(). + + \sa home(), currentPath(), rootPath(), tempPath() +*/ +QString QDir::homePath() +{ + return QFileSystemEngine::homePath(); +} + +/*! + \fn QString QDir::homeDirPath() + + Returns the absolute path of the user's home directory. + + Use homePath() instead. + + \sa homePath() +*/ + +/*! + \fn QDir QDir::temp() + + Returns the system's temporary directory. + + The directory is constructed using the absolute path of the temporary directory, + ensuring that its path() will be the same as its absolutePath(). + + See tempPath() for details. + + \sa drives(), current(), home(), root() +*/ + +/*! + Returns the absolute path of the system's temporary directory. + + On Unix/Linux systems this is the path in the \c TMPDIR environment + variable or \c{/tmp} if \c TMPDIR is not defined. On Windows this is + usually the path in the \c TEMP or \c TMP environment + variable. Whether a directory separator is added to the end or + not, depends on the operating system. + + \sa temp(), currentPath(), homePath(), rootPath() +*/ +QString QDir::tempPath() +{ + return QFileSystemEngine::tempPath(); +} + +/*! + \fn QDir QDir::root() + + Returns the root directory. + + The directory is constructed using the absolute path of the root directory, + ensuring that its path() will be the same as its absolutePath(). + + See rootPath() for details. + + \sa drives(), current(), home(), temp() +*/ + +/*! + Returns the absolute path of the root directory. + + For Unix operating systems this returns "/". For Windows file + systems this normally returns "c:/". On Symbian this typically returns + "c:/data", i.e. the same as native PathInfo::PhoneMemoryRootPath(). + + \sa root(), drives(), currentPath(), homePath(), tempPath() +*/ +QString QDir::rootPath() +{ + return QFileSystemEngine::rootPath(); +} + +/*! + \fn QString QDir::rootDirPath() + + Returns the absolute path of the root directory. + + Use rootPath() instead. + + \sa rootPath() +*/ + +#ifndef QT_NO_REGEXP +/*! + \overload + + Returns true if the \a fileName matches any of the wildcard (glob) + patterns in the list of \a filters; otherwise returns false. The + matching is case insensitive. + + \sa {QRegExp wildcard matching}, QRegExp::exactMatch() entryList() entryInfoList() +*/ +bool QDir::match(const QStringList &filters, const QString &fileName) +{ + for (QStringList::ConstIterator sit = filters.constBegin(); sit != filters.constEnd(); ++sit) { + QRegExp rx(*sit, Qt::CaseInsensitive, QRegExp::Wildcard); + if (rx.exactMatch(fileName)) + return true; + } + return false; +} + +/*! + Returns true if the \a fileName matches the wildcard (glob) + pattern \a filter; otherwise returns false. The \a filter may + contain multiple patterns separated by spaces or semicolons. + The matching is case insensitive. + + \sa {QRegExp wildcard matching}, QRegExp::exactMatch() entryList() entryInfoList() +*/ +bool QDir::match(const QString &filter, const QString &fileName) +{ + return match(nameFiltersFromString(filter), fileName); +} +#endif // QT_NO_REGEXP + +/*! + Removes all multiple directory separators "/" and resolves any + "."s or ".."s found in the path, \a path. + + Symbolic links are kept. This function does not return the + canonical path, but rather the simplest version of the input. + For example, "./local" becomes "local", "local/../bin" becomes + "bin" and "/local/usr/../bin" becomes "/local/bin". + + \sa absolutePath() canonicalPath() +*/ +QString QDir::cleanPath(const QString &path) +{ + if (path.isEmpty()) + return path; + QString name = path; + QChar dir_separator = separator(); + if (dir_separator != QLatin1Char('/')) + name.replace(dir_separator, QLatin1Char('/')); + + int used = 0, levels = 0; + const int len = name.length(); + QVarLengthArray<QChar> outVector(len); + QChar *out = outVector.data(); + + const QChar *p = name.unicode(); + for (int i = 0, last = -1, iwrite = 0; i < len; ++i) { + if (p[i] == QLatin1Char('/')) { + while (i < len-1 && p[i+1] == QLatin1Char('/')) { +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) //allow unc paths + if (!i) + break; +#endif + i++; + } + bool eaten = false; + if (i < len - 1 && p[i+1] == QLatin1Char('.')) { + int dotcount = 1; + if (i < len - 2 && p[i+2] == QLatin1Char('.')) + dotcount++; + if (i == len - dotcount - 1) { + if (dotcount == 1) { + break; + } else if (levels) { + if (last == -1) { + for (int i2 = iwrite-1; i2 >= 0; i2--) { + if (out[i2] == QLatin1Char('/')) { + last = i2; + break; + } + } + } + used -= iwrite - last - 1; + break; + } + } else if (p[i+dotcount+1] == QLatin1Char('/')) { + if (dotcount == 2 && levels) { + if (last == -1 || iwrite - last == 1) { + for (int i2 = (last == -1) ? (iwrite-1) : (last-1); i2 >= 0; i2--) { + if (out[i2] == QLatin1Char('/')) { + eaten = true; + last = i2; + break; + } + } + } else { + eaten = true; + } + if (eaten) { + levels--; + used -= iwrite - last; + iwrite = last; + last = -1; + } + } else if (dotcount == 2 && i > 0 && p[i - 1] != QLatin1Char('.')) { + eaten = true; + used -= iwrite - qMax(0, last); + iwrite = qMax(0, last); + last = -1; + ++i; + } else if (dotcount == 1) { + eaten = true; + } + if (eaten) + i += dotcount; + } else { + levels++; + } + } else if (last != -1 && iwrite - last == 1) { +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + eaten = (iwrite > 2); +#else + eaten = true; +#endif + last = -1; + } else if (last != -1 && i == len-1) { + eaten = true; + } else { + levels++; + } + if (!eaten) + last = i - (i - iwrite); + else + continue; + } else if (!i && p[i] == QLatin1Char('.')) { + int dotcount = 1; + if (len >= 1 && p[1] == QLatin1Char('.')) + dotcount++; + if (len >= dotcount && p[dotcount] == QLatin1Char('/')) { + if (dotcount == 1) { + i++; + while (i+1 < len-1 && p[i+1] == QLatin1Char('/')) + i++; + continue; + } + } + } + out[iwrite++] = p[i]; + used++; + } + + QString ret = (used == len ? name : QString(out, used)); + // Strip away last slash except for root directories + if (ret.length() > 1 && ret.endsWith(QLatin1Char('/'))) { +#if defined (Q_OS_WIN) || defined (Q_OS_SYMBIAN) + if (!(ret.length() == 3 && ret.at(1) == QLatin1Char(':'))) +#endif + ret.chop(1); + } + + return ret; +} + +/*! + Returns true if \a path is relative; returns false if it is + absolute. + + \sa isRelative() isAbsolutePath() makeAbsolute() +*/ +bool QDir::isRelativePath(const QString &path) +{ + return QFileInfo(path).isRelative(); +} + +/*! + Refreshes the directory information. +*/ +void QDir::refresh() const +{ + QDirPrivate *d = const_cast<QDir*>(this)->d_ptr.data(); + d->metaData.clear(); + d->initFileEngine(); + d->clearFileLists(); +} + +/*! + \internal + + Returns a list of name filters from the given \a nameFilter. (If + there is more than one filter, each pair of filters is separated + by a space or by a semicolon.) +*/ +QStringList QDir::nameFiltersFromString(const QString &nameFilter) +{ + return QDirPrivate::splitFilters(nameFilter); +} + +/*! + \macro void Q_INIT_RESOURCE(name) + \relates QDir + + Initializes the resources specified by the \c .qrc file with the + specified base \a name. Normally, Qt resources are loaded + automatically at startup. The Q_INIT_RESOURCE() macro is + necessary on some platforms for resources stored in a static + library. + + For example, if your application's resources are listed in a file + called \c myapp.qrc, you can ensure that the resources are + initialized at startup by adding this line to your \c main() + function: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 13 + + If the file name contains characters that cannot be part of a valid C++ function name + (such as '-'), they have to be replaced by the underscore character ('_'). + + Note: This macro cannot be used in a namespace. It should be called from + main(). If that is not possible, the following workaround can be used + to init the resource \c myapp from the function \c{MyNamespace::myFunction}: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 14 + + \sa Q_CLEANUP_RESOURCE(), {The Qt Resource System} +*/ + +/*! + \since 4.1 + \macro void Q_CLEANUP_RESOURCE(name) + \relates QDir + + Unloads the resources specified by the \c .qrc file with the base + name \a name. + + Normally, Qt resources are unloaded automatically when the + application terminates, but if the resources are located in a + plugin that is being unloaded, call Q_CLEANUP_RESOURCE() to force + removal of your resources. + + Note: This macro cannot be used in a namespace. Please see the + Q_INIT_RESOURCE documentation for a workaround. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qdir.cpp 15 + + \sa Q_INIT_RESOURCE(), {The Qt Resource System} +*/ + +#ifdef QT3_SUPPORT + +/*! + \fn bool QDir::matchAllDirs() const + + Use filter() & AllDirs instead. +*/ +bool QDir::matchAllDirs() const +{ + const QDirPrivate* d = d_ptr.constData(); + return d->matchAllDirs; +} + + +/*! + \fn void QDir::setMatchAllDirs(bool on) + + Use setFilter() instead. +*/ +void QDir::setMatchAllDirs(bool on) +{ + QDirPrivate* d = d_ptr.data(); + d->initFileEngine(); + d->clearFileLists(); + + d->matchAllDirs = on; +} + +/*! + Use nameFilters() instead. +*/ +QString QDir::nameFilter() const +{ + const QDirPrivate* d = d_ptr.constData(); + return nameFilters().join(QString(d->filterSepChar)); +} + +/*! + Use setNameFilters() instead. + + The \a nameFilter is a wildcard (globbing) filter that understands + "*" and "?" wildcards. (See \l{QRegExp wildcard matching}.) You may + specify several filter entries, each separated by spaces or by + semicolons. + + For example, if you want entryList() and entryInfoList() to list + all files ending with either ".cpp" or ".h", you would use either + dir.setNameFilters("*.cpp *.h") or dir.setNameFilters("*.cpp;*.h"). + + \oldcode + QString filter = "*.cpp *.cxx *.cc"; + dir.setNameFilter(filter); + \newcode + QString filter = "*.cpp *.cxx *.cc"; + dir.setNameFilters(filter.split(' ')); + \endcode +*/ +void QDir::setNameFilter(const QString &nameFilter) +{ + QDirPrivate* d = d_ptr.data(); + d->initFileEngine(); + d->clearFileLists(); + + d->filterSepChar = QDirPrivate::getFilterSepChar(nameFilter); + d->nameFilters = QDirPrivate::splitFilters(nameFilter, d->filterSepChar); +} + +/*! + \fn QString QDir::absPath() const + + Use absolutePath() instead. +*/ + +/*! + \fn QString QDir::absFilePath(const QString &fileName, bool acceptAbsPath) const + + Use absoluteFilePath(\a fileName) instead. + + The \a acceptAbsPath parameter is ignored. +*/ + +/*! + \fn bool QDir::mkdir(const QString &dirName, bool acceptAbsPath) const + + Use mkdir(\a dirName) instead. + + The \a acceptAbsPath parameter is ignored. +*/ + +/*! + \fn bool QDir::rmdir(const QString &dirName, bool acceptAbsPath) const + + Use rmdir(\a dirName) instead. + + The \a acceptAbsPath parameter is ignored. +*/ + +/*! + \fn QStringList QDir::entryList(const QString &nameFilter, Filters filters, + SortFlags sort) const + \overload + + Use the overload that takes a name filter string list as first + argument instead of a combination of attribute filter flags. +*/ + +/*! + \fn QFileInfoList QDir::entryInfoList(const QString &nameFilter, Filters filters, + SortFlags sort) const + \overload + + Use the overload that takes a name filter string list as first + argument instead of a combination of attribute filter flags. +*/ + +/*! + \fn void QDir::convertToAbs() + + Use makeAbsolute() instead. +*/ + +/*! + \fn QString QDir::cleanDirPath(const QString &name) + + Use cleanPath() instead. +*/ + +/*! + \typedef QDir::FilterSpec + + Use QDir::Filters instead. +*/ + +/*! + \typedef QDir::SortSpec + + Use QDir::SortFlags instead. +*/ +#endif // QT3_SUPPORT + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, QDir::Filters filters) +{ + QStringList flags; + if (filters == QDir::NoFilter) { + flags << QLatin1String("NoFilter"); + } else { + if (filters & QDir::Dirs) flags << QLatin1String("Dirs"); + if (filters & QDir::AllDirs) flags << QLatin1String("AllDirs"); + if (filters & QDir::Files) flags << QLatin1String("Files"); + if (filters & QDir::Drives) flags << QLatin1String("Drives"); + if (filters & QDir::NoSymLinks) flags << QLatin1String("NoSymLinks"); + if (filters & QDir::NoDotAndDotDot) flags << QLatin1String("NoDotAndDotDot"); // ### Qt5: remove (because NoDotAndDotDot=NoDot|NoDotDot) + if (filters & QDir::NoDot) flags << QLatin1String("NoDot"); + if (filters & QDir::NoDotDot) flags << QLatin1String("NoDotDot"); + if ((filters & QDir::AllEntries) == QDir::AllEntries) flags << QLatin1String("AllEntries"); + if (filters & QDir::Readable) flags << QLatin1String("Readable"); + if (filters & QDir::Writable) flags << QLatin1String("Writable"); + if (filters & QDir::Executable) flags << QLatin1String("Executable"); + if (filters & QDir::Modified) flags << QLatin1String("Modified"); + if (filters & QDir::Hidden) flags << QLatin1String("Hidden"); + if (filters & QDir::System) flags << QLatin1String("System"); + if (filters & QDir::CaseSensitive) flags << QLatin1String("CaseSensitive"); + } + debug << "QDir::Filters(" << qPrintable(flags.join(QLatin1String("|"))) << ')'; + return debug; +} + +static QDebug operator<<(QDebug debug, QDir::SortFlags sorting) +{ + if (sorting == QDir::NoSort) { + debug << "QDir::SortFlags(NoSort)"; + } else { + QString type; + if ((sorting & 3) == QDir::Name) type = QLatin1String("Name"); + if ((sorting & 3) == QDir::Time) type = QLatin1String("Time"); + if ((sorting & 3) == QDir::Size) type = QLatin1String("Size"); + if ((sorting & 3) == QDir::Unsorted) type = QLatin1String("Unsorted"); + + QStringList flags; + if (sorting & QDir::DirsFirst) flags << QLatin1String("DirsFirst"); + if (sorting & QDir::DirsLast) flags << QLatin1String("DirsLast"); + if (sorting & QDir::IgnoreCase) flags << QLatin1String("IgnoreCase"); + if (sorting & QDir::LocaleAware) flags << QLatin1String("LocaleAware"); + if (sorting & QDir::Type) flags << QLatin1String("Type"); + debug << "QDir::SortFlags(" << qPrintable(type) + << '|' + << qPrintable(flags.join(QLatin1String("|"))) << ')'; + } + return debug; +} + +QDebug operator<<(QDebug debug, const QDir &dir) +{ + debug.maybeSpace() << "QDir(" << dir.path() + << ", nameFilters = {" + << qPrintable(dir.nameFilters().join(QLatin1String(","))) + << "}, " + << dir.sorting() + << ',' + << dir.filter() + << ')'; + return debug.space(); +} +#endif // QT_NO_DEBUG_STREAM + +QT_END_NAMESPACE diff --git a/src/corelib/io/qdir.h b/src/corelib/io/qdir.h new file mode 100644 index 0000000000..18049403a8 --- /dev/null +++ b/src/corelib/io/qdir.h @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIR_H +#define QDIR_H + +#include <QtCore/qstring.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QDirPrivate; + +class Q_CORE_EXPORT QDir +{ +protected: + QSharedDataPointer<QDirPrivate> d_ptr; + +public: + enum Filter { Dirs = 0x001, + Files = 0x002, + Drives = 0x004, + NoSymLinks = 0x008, + AllEntries = Dirs | Files | Drives, + TypeMask = 0x00f, +#ifdef QT3_SUPPORT + All = AllEntries, +#endif + + Readable = 0x010, + Writable = 0x020, + Executable = 0x040, + PermissionMask = 0x070, +#ifdef QT3_SUPPORT + RWEMask = 0x070, +#endif + + Modified = 0x080, + Hidden = 0x100, + System = 0x200, + + AccessMask = 0x3F0, + + AllDirs = 0x400, + CaseSensitive = 0x800, + NoDotAndDotDot = 0x1000, // ### Qt5 NoDotAndDotDot = NoDot|NoDotDot + NoDot = 0x2000, + NoDotDot = 0x4000, + + NoFilter = -1 +#ifdef QT3_SUPPORT + ,DefaultFilter = NoFilter +#endif + }; + Q_DECLARE_FLAGS(Filters, Filter) +#ifdef QT3_SUPPORT + typedef Filters FilterSpec; +#endif + + enum SortFlag { Name = 0x00, + Time = 0x01, + Size = 0x02, + Unsorted = 0x03, + SortByMask = 0x03, + + DirsFirst = 0x04, + Reversed = 0x08, + IgnoreCase = 0x10, + DirsLast = 0x20, + LocaleAware = 0x40, + Type = 0x80, + NoSort = -1 +#ifdef QT3_SUPPORT + ,DefaultSort = NoSort +#endif + }; + Q_DECLARE_FLAGS(SortFlags, SortFlag) + + QDir(const QDir &); + QDir(const QString &path = QString()); + QDir(const QString &path, const QString &nameFilter, + SortFlags sort = SortFlags(Name | IgnoreCase), Filters filter = AllEntries); + ~QDir(); + + QDir &operator=(const QDir &); + QDir &operator=(const QString &path); +#ifdef Q_COMPILER_RVALUE_REFS + inline QDir &operator=(QDir &&other) + { qSwap(d_ptr, other.d_ptr); return *this; } +#endif + + void setPath(const QString &path); + QString path() const; + QString absolutePath() const; + QString canonicalPath() const; + + static void addResourceSearchPath(const QString &path); + + static void setSearchPaths(const QString &prefix, const QStringList &searchPaths); + static void addSearchPath(const QString &prefix, const QString &path); + static QStringList searchPaths(const QString &prefix); + + QString dirName() const; + QString filePath(const QString &fileName) const; + QString absoluteFilePath(const QString &fileName) const; + QString relativeFilePath(const QString &fileName) const; + +#ifdef QT_DEPRECATED + QT_DEPRECATED static QString convertSeparators(const QString &pathName); +#endif + static QString toNativeSeparators(const QString &pathName); + static QString fromNativeSeparators(const QString &pathName); + + bool cd(const QString &dirName); + bool cdUp(); + + QStringList nameFilters() const; + void setNameFilters(const QStringList &nameFilters); + + Filters filter() const; + void setFilter(Filters filter); + SortFlags sorting() const; + void setSorting(SortFlags sort); + + uint count() const; + QString operator[](int) const; + + static QStringList nameFiltersFromString(const QString &nameFilter); + + QStringList entryList(Filters filters = NoFilter, SortFlags sort = NoSort) const; + QStringList entryList(const QStringList &nameFilters, Filters filters = NoFilter, + SortFlags sort = NoSort) const; + + QFileInfoList entryInfoList(Filters filters = NoFilter, SortFlags sort = NoSort) const; + QFileInfoList entryInfoList(const QStringList &nameFilters, Filters filters = NoFilter, + SortFlags sort = NoSort) const; + + bool mkdir(const QString &dirName) const; + bool rmdir(const QString &dirName) const; + bool mkpath(const QString &dirPath) const; + bool rmpath(const QString &dirPath) const; + + bool isReadable() const; + bool exists() const; + bool isRoot() const; + + static bool isRelativePath(const QString &path); + inline static bool isAbsolutePath(const QString &path) { return !isRelativePath(path); } + bool isRelative() const; + inline bool isAbsolute() const { return !isRelative(); } + bool makeAbsolute(); + + bool operator==(const QDir &dir) const; + inline bool operator!=(const QDir &dir) const { return !operator==(dir); } + + bool remove(const QString &fileName); + bool rename(const QString &oldName, const QString &newName); + bool exists(const QString &name) const; + + static QFileInfoList drives(); + + static QChar separator(); + + static bool setCurrent(const QString &path); + static inline QDir current() { return QDir(currentPath()); } + static QString currentPath(); + + static inline QDir home() { return QDir(homePath()); } + static QString homePath(); + static inline QDir root() { return QDir(rootPath()); } + static QString rootPath(); + static inline QDir temp() { return QDir(tempPath()); } + static QString tempPath(); + +#ifndef QT_NO_REGEXP + static bool match(const QStringList &filters, const QString &fileName); + static bool match(const QString &filter, const QString &fileName); +#endif + + static QString cleanPath(const QString &path); + void refresh() const; + +#ifdef QT3_SUPPORT + typedef SortFlags SortSpec; + inline QT3_SUPPORT QString absPath() const { return absolutePath(); } + inline QT3_SUPPORT QString absFilePath(const QString &fileName, bool acceptAbsPath = true) const + { Q_UNUSED(acceptAbsPath); return absoluteFilePath(fileName); } + QT3_SUPPORT bool matchAllDirs() const; + QT3_SUPPORT void setMatchAllDirs(bool on); + inline QT3_SUPPORT QStringList entryList(const QString &nameFilter, Filters filters = NoFilter, + SortFlags sort = NoSort) const + { return entryList(nameFiltersFromString(nameFilter), filters, sort); } + inline QT3_SUPPORT QFileInfoList entryInfoList(const QString &nameFilter, + Filters filters = NoFilter, + SortFlags sort = NoSort) const + { return entryInfoList(nameFiltersFromString(nameFilter), filters, sort); } + + QT3_SUPPORT QString nameFilter() const; + QT3_SUPPORT void setNameFilter(const QString &nameFilter); + + inline QT3_SUPPORT bool mkdir(const QString &dirName, bool acceptAbsPath) const + { Q_UNUSED(acceptAbsPath); return mkdir(dirName); } + inline QT3_SUPPORT bool rmdir(const QString &dirName, bool acceptAbsPath) const + { Q_UNUSED(acceptAbsPath); return rmdir(dirName); } + + inline QT3_SUPPORT void convertToAbs() { makeAbsolute(); } + inline QT3_SUPPORT static QString currentDirPath() { return currentPath(); } + inline QT3_SUPPORT static QString homeDirPath() { return homePath(); } + inline QT3_SUPPORT static QString rootDirPath() { return rootPath(); } + inline QT3_SUPPORT static QString cleanDirPath(const QString &name) { return cleanPath(name); } +#endif // QT3_SUPPORT +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDir::Filters) +Q_DECLARE_OPERATORS_FOR_FLAGS(QDir::SortFlags) + +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +Q_CORE_EXPORT QDebug operator<<(QDebug debug, QDir::Filters filters); +Q_CORE_EXPORT QDebug operator<<(QDebug debug, const QDir &dir); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDIR_H diff --git a/src/corelib/io/qdir_p.h b/src/corelib/io/qdir_p.h new file mode 100644 index 0000000000..7f77a84866 --- /dev/null +++ b/src/corelib/io/qdir_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIR_PRIVATE_H +#define QDIR_PRIVATE_H + +#include "qfilesystementry_p.h" +#include "qfilesystemmetadata_p.h" + +QT_BEGIN_NAMESPACE + +class QDirPrivate : public QSharedData +{ +public: + QDirPrivate(const QString &path, const QStringList &nameFilters_ = QStringList(), + QDir::SortFlags sort_ = QDir::SortFlags(QDir::Name | QDir::IgnoreCase), + QDir::Filters filters_ = QDir::AllEntries); + + QDirPrivate(const QDirPrivate ©); + + bool exists() const; + + void initFileEngine(); + void initFileLists(const QDir &dir) const; + + static void sortFileList(QDir::SortFlags, QFileInfoList &, QStringList *, QFileInfoList *); + + static inline QChar getFilterSepChar(const QString &nameFilter); + + static inline QStringList splitFilters(const QString &nameFilter, QChar sep = 0); + + inline void setPath(const QString &path); + + inline void clearFileLists(); + + inline void resolveAbsoluteEntry() const; + + QStringList nameFilters; + QDir::SortFlags sort; + QDir::Filters filters; + +#ifdef QT3_SUPPORT + QChar filterSepChar; + bool matchAllDirs; +#endif + + QScopedPointer<QAbstractFileEngine> fileEngine; + + mutable bool fileListsInitialized; + mutable QStringList files; + mutable QFileInfoList fileInfos; + + QFileSystemEntry dirEntry; + mutable QFileSystemEntry absoluteDirEntry; + mutable QFileSystemMetaData metaData; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/corelib/io/qdiriterator.cpp b/src/corelib/io/qdiriterator.cpp new file mode 100644 index 0000000000..4a538f33db --- /dev/null +++ b/src/corelib/io/qdiriterator.cpp @@ -0,0 +1,561 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \since 4.3 + \class QDirIterator + \brief The QDirIterator class provides an iterator for directory entrylists. + + You can use QDirIterator to navigate entries of a directory one at a time. + It is similar to QDir::entryList() and QDir::entryInfoList(), but because + it lists entries one at a time instead of all at once, it scales better + and is more suitable for large directories. It also supports listing + directory contents recursively, and following symbolic links. Unlike + QDir::entryList(), QDirIterator does not support sorting. + + The QDirIterator constructor takes a QDir or a directory as + argument. After construction, the iterator is located before the first + directory entry. Here's how to iterate over all the entries sequentially: + + \snippet doc/src/snippets/code/src_corelib_io_qdiriterator.cpp 0 + + The next() function returns the path to the next directory entry and + advances the iterator. You can also call filePath() to get the current + file path without advancing the iterator. The fileName() function returns + only the name of the file, similar to how QDir::entryList() works. You can + also call fileInfo() to get a QFileInfo for the current entry. + + Unlike Qt's container iterators, QDirIterator is uni-directional (i.e., + you cannot iterate directories in reverse order) and does not allow random + access. + + QDirIterator works with all supported file engines, and is implemented + using QAbstractFileEngineIterator. + + \sa QDir, QDir::entryList(), QAbstractFileEngineIterator +*/ + +/*! \enum QDirIterator::IteratorFlag + + This enum describes flags that you can combine to configure the behavior + of QDirIterator. + + \value NoIteratorFlags The default value, representing no flags. The + iterator will return entries for the assigned path. + + \value Subdirectories List entries inside all subdirectories as well. + + \value FollowSymlinks When combined with Subdirectories, this flag + enables iterating through all subdirectories of the assigned path, + following all symbolic links. Symbolic link loops (e.g., "link" => "." or + "link" => "..") are automatically detected and ignored. +*/ + +#include "qdiriterator.h" +#include "qdir_p.h" + +#include "qabstractfileengine.h" + +#include <QtCore/qset.h> +#include <QtCore/qstack.h> +#include <QtCore/qvariant.h> + +#include <QtCore/private/qfilesystemiterator_p.h> +#include <QtCore/private/qfilesystementry_p.h> +#include <QtCore/private/qfilesystemmetadata_p.h> +#include <QtCore/private/qfilesystemengine_p.h> +#include <QtCore/qfsfileengine.h> +#include <QtCore/private/qfileinfo_p.h> + +QT_BEGIN_NAMESPACE + +template <class Iterator> +class QDirIteratorPrivateIteratorStack : public QStack<Iterator *> +{ +public: + ~QDirIteratorPrivateIteratorStack() + { + qDeleteAll(*this); + } +}; + +class QDirIteratorPrivate +{ +public: + QDirIteratorPrivate(const QFileSystemEntry &entry, const QStringList &nameFilters, + QDir::Filters filters, QDirIterator::IteratorFlags flags, bool resolveEngine = true); + + void advance(); + + bool entryMatches(const QString & fileName, const QFileInfo &fileInfo); + void pushDirectory(const QFileInfo &fileInfo); + void checkAndPushDirectory(const QFileInfo &); + bool matchesFilters(const QString &fileName, const QFileInfo &fi) const; + + QScopedPointer<QAbstractFileEngine> engine; + + QFileSystemEntry dirEntry; + const QStringList nameFilters; + const QDir::Filters filters; + const QDirIterator::IteratorFlags iteratorFlags; + +#ifndef QT_NO_REGEXP + QVector<QRegExp> nameRegExps; +#endif + + QDirIteratorPrivateIteratorStack<QAbstractFileEngineIterator> fileEngineIterators; +#ifndef QT_NO_FILESYSTEMITERATOR + QDirIteratorPrivateIteratorStack<QFileSystemIterator> nativeIterators; +#endif + + QFileInfo currentFileInfo; + QFileInfo nextFileInfo; + + // Loop protection + QSet<QString> visitedLinks; +}; + +/*! + \internal +*/ +QDirIteratorPrivate::QDirIteratorPrivate(const QFileSystemEntry &entry, const QStringList &nameFilters, + QDir::Filters filters, QDirIterator::IteratorFlags flags, bool resolveEngine) + : dirEntry(entry) + , nameFilters(nameFilters.contains(QLatin1String("*")) ? QStringList() : nameFilters) + , filters(QDir::NoFilter == filters ? QDir::AllEntries : filters) + , iteratorFlags(flags) +{ +#ifndef QT_NO_REGEXP + nameRegExps.reserve(nameFilters.size()); + for (int i = 0; i < nameFilters.size(); ++i) + nameRegExps.append( + QRegExp(nameFilters.at(i), + (filters & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive, + QRegExp::Wildcard)); +#endif + QFileSystemMetaData metaData; + if (resolveEngine) + engine.reset(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(dirEntry, metaData)); + QFileInfo fileInfo(new QFileInfoPrivate(dirEntry, metaData)); + + // Populate fields for hasNext() and next() + pushDirectory(fileInfo); + advance(); +} + +/*! + \internal +*/ +void QDirIteratorPrivate::pushDirectory(const QFileInfo &fileInfo) +{ + QString path = fileInfo.filePath(); + +#ifdef Q_OS_WIN + if (fileInfo.isSymLink()) + path = fileInfo.canonicalFilePath(); +#endif + + if (iteratorFlags & QDirIterator::FollowSymlinks) + visitedLinks << fileInfo.canonicalFilePath(); + + if (engine) { + engine->setFileName(path); + QAbstractFileEngineIterator *it = engine->beginEntryList(filters, nameFilters); + if (it) { + it->setPath(path); + fileEngineIterators << it; + } else { + // No iterator; no entry list. + } + } else { +#ifndef QT_NO_FILESYSTEMITERATOR + QFileSystemIterator *it = new QFileSystemIterator(fileInfo.d_ptr->fileEntry, + filters, nameFilters, iteratorFlags); + nativeIterators << it; +#endif + } +} + +inline bool QDirIteratorPrivate::entryMatches(const QString & fileName, const QFileInfo &fileInfo) +{ + checkAndPushDirectory(fileInfo); + + if (matchesFilters(fileName, fileInfo)) { + currentFileInfo = nextFileInfo; + nextFileInfo = fileInfo; + + //We found a matching entry. + return true; + } + + return false; +} + +/*! + \internal +*/ +void QDirIteratorPrivate::advance() +{ + if (engine) { + while (!fileEngineIterators.isEmpty()) { + // Find the next valid iterator that matches the filters. + QAbstractFileEngineIterator *it; + while (it = fileEngineIterators.top(), it->hasNext()) { + it->next(); + if (entryMatches(it->currentFileName(), it->currentFileInfo())) + return; + } + + fileEngineIterators.pop(); + delete it; + } + } else { +#ifndef QT_NO_FILESYSTEMITERATOR + QFileSystemEntry nextEntry; + QFileSystemMetaData nextMetaData; + + while (!nativeIterators.isEmpty()) { + // Find the next valid iterator that matches the filters. + QFileSystemIterator *it; + while (it = nativeIterators.top(), it->advance(nextEntry, nextMetaData)) { + QFileInfo info(new QFileInfoPrivate(nextEntry, nextMetaData)); + + if (entryMatches(nextEntry.fileName(), info)) + return; + } + + nativeIterators.pop(); + delete it; + } +#endif + } + + currentFileInfo = nextFileInfo; + nextFileInfo = QFileInfo(); +} + +/*! + \internal + */ +void QDirIteratorPrivate::checkAndPushDirectory(const QFileInfo &fileInfo) +{ + // If we're doing flat iteration, we're done. + if (!(iteratorFlags & QDirIterator::Subdirectories)) + return; + + // Never follow non-directory entries + if (!fileInfo.isDir()) + return; + + // Follow symlinks only when asked + if (!(iteratorFlags & QDirIterator::FollowSymlinks) && fileInfo.isSymLink()) + return; + + // Never follow . and .. + QString fileName = fileInfo.fileName(); + if (QLatin1String(".") == fileName || QLatin1String("..") == fileName) + return; + + // No hidden directories unless requested + if (!(filters & QDir::AllDirs) && !(filters & QDir::Hidden) && fileInfo.isHidden()) + return; + + // Stop link loops + if (!visitedLinks.isEmpty() && + visitedLinks.contains(fileInfo.canonicalFilePath())) + return; + + pushDirectory(fileInfo); +} + +/*! + \internal + + This convenience function implements the iterator's filtering logics and + applies then to the current directory entry. + + It returns true if the current entry matches the filters (i.e., the + current entry will be returned as part of the directory iteration); + otherwise, false is returned. +*/ + +bool QDirIteratorPrivate::matchesFilters(const QString &fileName, const QFileInfo &fi) const +{ + Q_ASSERT(!fileName.isEmpty()); + + // filter . and ..? + const int fileNameSize = fileName.size(); + const bool dotOrDotDot = fileName[0] == QLatin1Char('.') + && ((fileNameSize == 1) + ||(fileNameSize == 2 && fileName[1] == QLatin1Char('.'))); + if ((filters & QDir::NoDot) && dotOrDotDot && fileNameSize == 1) + return false; + if ((filters & QDir::NoDotDot) && dotOrDotDot && fileNameSize == 2) + return false; + if ((filters & QDir::NoDotAndDotDot) && dotOrDotDot) // ### Qt5 remove (NoDotAndDotDot == NoDot|NoDotDot) + return false; + + // name filter +#ifndef QT_NO_REGEXP + // Pass all entries through name filters, except dirs if the AllDirs + if (!nameFilters.isEmpty() && !((filters & QDir::AllDirs) && fi.isDir())) { + bool matched = false; + for (QVector<QRegExp>::const_iterator iter = nameRegExps.constBegin(), + end = nameRegExps.constEnd(); + iter != end; ++iter) { + + if (iter->exactMatch(fileName)) { + matched = true; + break; + } + } + if (!matched) + return false; + } +#endif + // skip symlinks + const bool skipSymlinks = (filters & QDir::NoSymLinks); + const bool includeSystem = (filters & QDir::System); + if(skipSymlinks && fi.isSymLink()) { + // The only reason to save this file is if it is a broken link and we are requesting system files. + if(!includeSystem || fi.exists()) + return false; + } + + // filter hidden + const bool includeHidden = (filters & QDir::Hidden); + if (!includeHidden && !dotOrDotDot && fi.isHidden()) + return false; + + // filter system files + if (!includeSystem && (!(fi.isFile() || fi.isDir() || fi.isSymLink()) + || (!fi.exists() && fi.isSymLink()))) + return false; + + // skip directories + const bool skipDirs = !(filters & (QDir::Dirs | QDir::AllDirs)); + if (skipDirs && fi.isDir()) + return false; + + // skip files + const bool skipFiles = !(filters & QDir::Files); + if (skipFiles && fi.isFile()) + // Basically we need a reason not to exclude this file otherwise we just eliminate it. + return false; + + // filter permissions + const bool filterPermissions = ((filters & QDir::PermissionMask) + && (filters & QDir::PermissionMask) != QDir::PermissionMask); + const bool doWritable = !filterPermissions || (filters & QDir::Writable); + const bool doExecutable = !filterPermissions || (filters & QDir::Executable); + const bool doReadable = !filterPermissions || (filters & QDir::Readable); + if (filterPermissions + && ((doReadable && !fi.isReadable()) + || (doWritable && !fi.isWritable()) + || (doExecutable && !fi.isExecutable()))) { + return false; + } + + return true; +} + +/*! + Constructs a QDirIterator that can iterate over \a dir's entrylist, using + \a dir's name filters and regular filters. You can pass options via \a + flags to decide how the directory should be iterated. + + By default, \a flags is NoIteratorFlags, which provides the same behavior + as in QDir::entryList(). + + The sorting in \a dir is ignored. + + \note To list symlinks that point to non existing files, QDir::System must be + passed to the flags. + + \sa hasNext(), next(), IteratorFlags +*/ +QDirIterator::QDirIterator(const QDir &dir, IteratorFlags flags) +{ + // little trick to get hold of the QDirPrivate while there is no API on QDir to give it to us + class MyQDir : public QDir { public: const QDirPrivate *priv() const { return d_ptr.constData(); } }; + const QDirPrivate *other = static_cast<const MyQDir*>(&dir)->priv(); + d.reset(new QDirIteratorPrivate(other->dirEntry, other->nameFilters, other->filters, flags, !other->fileEngine.isNull())); +} + +/*! + Constructs a QDirIterator that can iterate over \a path, with no name + filtering and \a filters for entry filtering. You can pass options via \a + flags to decide how the directory should be iterated. + + By default, \a filters is QDir::NoFilter, and \a flags is NoIteratorFlags, + which provides the same behavior as in QDir::entryList(). + + \note To list symlinks that point to non existing files, QDir::System must be + passed to the flags. + + \sa hasNext(), next(), IteratorFlags +*/ +QDirIterator::QDirIterator(const QString &path, QDir::Filters filters, IteratorFlags flags) + : d(new QDirIteratorPrivate(QFileSystemEntry(path), QStringList(), filters, flags)) +{ +} + +/*! + Constructs a QDirIterator that can iterate over \a path. You can pass + options via \a flags to decide how the directory should be iterated. + + By default, \a flags is NoIteratorFlags, which provides the same behavior + as in QDir::entryList(). + + \note To list symlinks that point to non existing files, QDir::System must be + passed to the flags. + + \sa hasNext(), next(), IteratorFlags +*/ +QDirIterator::QDirIterator(const QString &path, IteratorFlags flags) + : d(new QDirIteratorPrivate(QFileSystemEntry(path), QStringList(), QDir::NoFilter, flags)) +{ +} + +/*! + Constructs a QDirIterator that can iterate over \a path, using \a + nameFilters and \a filters. You can pass options via \a flags to decide + how the directory should be iterated. + + By default, \a flags is NoIteratorFlags, which provides the same behavior + as QDir::entryList(). + + \note To list symlinks that point to non existing files, QDir::System must be + passed to the flags. + + \sa hasNext(), next(), IteratorFlags +*/ +QDirIterator::QDirIterator(const QString &path, const QStringList &nameFilters, + QDir::Filters filters, IteratorFlags flags) + : d(new QDirIteratorPrivate(QFileSystemEntry(path), nameFilters, filters, flags)) +{ +} + +/*! + Destroys the QDirIterator. +*/ +QDirIterator::~QDirIterator() +{ +} + +/*! + Advances the iterator to the next entry, and returns the file path of this + new entry. If hasNext() returns false, this function does nothing, and + returns a null QString. + + You can call fileName() or filePath() to get the current entry file name + or path, or fileInfo() to get a QFileInfo for the current entry. + + \sa hasNext(), fileName(), filePath(), fileInfo() +*/ +QString QDirIterator::next() +{ + d->advance(); + return filePath(); +} + +/*! + Returns true if there is at least one more entry in the directory; + otherwise, false is returned. + + \sa next(), fileName(), filePath(), fileInfo() +*/ +bool QDirIterator::hasNext() const +{ + if (d->engine) + return !d->fileEngineIterators.isEmpty(); + else +#ifndef QT_NO_FILESYSTEMITERATOR + return !d->nativeIterators.isEmpty(); +#else + return false; +#endif +} + +/*! + Returns the file name for the current directory entry, without the path + prepended. + + This function is convenient when iterating a single directory. When using + the QDirIterator::Subdirectories flag, you can use filePath() to get the + full path. + + \sa filePath(), fileInfo() +*/ +QString QDirIterator::fileName() const +{ + return d->currentFileInfo.fileName(); +} + +/*! + Returns the full file path for the current directory entry. + + \sa fileInfo(), fileName() +*/ +QString QDirIterator::filePath() const +{ + return d->currentFileInfo.filePath(); +} + +/*! + Returns a QFileInfo for the current directory entry. + + \sa filePath(), fileName() +*/ +QFileInfo QDirIterator::fileInfo() const +{ + return d->currentFileInfo; +} + +/*! + Returns the base directory of the iterator. +*/ +QString QDirIterator::path() const +{ + return d->dirEntry.filePath(); +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qdiriterator.h b/src/corelib/io/qdiriterator.h new file mode 100644 index 0000000000..df2213f120 --- /dev/null +++ b/src/corelib/io/qdiriterator.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIRITERATOR_H +#define QDIRITERATOR_H + +#include <QtCore/qdir.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QDirIteratorPrivate; +class Q_CORE_EXPORT QDirIterator { +public: + enum IteratorFlag { + NoIteratorFlags = 0x0, + FollowSymlinks = 0x1, + Subdirectories = 0x2 + }; + Q_DECLARE_FLAGS(IteratorFlags, IteratorFlag) + + QDirIterator(const QDir &dir, IteratorFlags flags = NoIteratorFlags); + QDirIterator(const QString &path, + IteratorFlags flags = NoIteratorFlags); + QDirIterator(const QString &path, + QDir::Filters filter, + IteratorFlags flags = NoIteratorFlags); + QDirIterator(const QString &path, + const QStringList &nameFilters, + QDir::Filters filters = QDir::NoFilter, + IteratorFlags flags = NoIteratorFlags); + + virtual ~QDirIterator(); + + QString next(); + bool hasNext() const; + + QString fileName() const; + QString filePath() const; + QFileInfo fileInfo() const; + QString path() const; + +private: + Q_DISABLE_COPY(QDirIterator) + + QScopedPointer<QDirIteratorPrivate> d; + friend class QDir; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDirIterator::IteratorFlags) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/corelib/io/qfile.cpp b/src/corelib/io/qfile.cpp new file mode 100644 index 0000000000..0ade573ea4 --- /dev/null +++ b/src/corelib/io/qfile.cpp @@ -0,0 +1,1884 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qdebug.h" +#include "qfile.h" +#include "qfsfileengine.h" +#include "qtemporaryfile.h" +#include "qlist.h" +#include "qfileinfo.h" +#include "private/qiodevice_p.h" +#include "private/qfile_p.h" +#include "private/qsystemerror_p.h" +#if defined(QT_BUILD_CORE_LIB) +# include "qcoreapplication.h" +#endif + +#ifdef QT_NO_QOBJECT +#define tr(X) QString::fromLatin1(X) +#endif + +QT_BEGIN_NAMESPACE + +static const int QFILE_WRITEBUFFER_SIZE = 16384; + +static QByteArray locale_encode(const QString &f) +{ +#if defined(Q_OS_DARWIN) + // Mac always expects UTF-8... and decomposed... + return f.normalized(QString::NormalizationForm_D).toUtf8(); +#elif defined(Q_OS_SYMBIAN) + return f.toUtf8(); +#else + return f.toLocal8Bit(); +#endif +} + +static QString locale_decode(const QByteArray &f) +{ +#if defined(Q_OS_DARWIN) + // Mac always gives us UTF-8 and decomposed, we want that composed... + return QString::fromUtf8(f).normalized(QString::NormalizationForm_C); +#elif defined(Q_OS_SYMBIAN) + return QString::fromUtf8(f); +#else + return QString::fromLocal8Bit(f); +#endif +} + +//************* QFilePrivate +QFile::EncoderFn QFilePrivate::encoder = locale_encode; +QFile::DecoderFn QFilePrivate::decoder = locale_decode; + +QFilePrivate::QFilePrivate() + : fileEngine(0), lastWasWrite(false), + writeBuffer(QFILE_WRITEBUFFER_SIZE), error(QFile::NoError), + cachedSize(0) +{ +} + +QFilePrivate::~QFilePrivate() +{ + delete fileEngine; + fileEngine = 0; +} + +bool +QFilePrivate::openExternalFile(int flags, int fd, QFile::FileHandleFlags handleFlags) +{ +#ifdef QT_NO_FSFILEENGINE + Q_UNUSED(flags); + Q_UNUSED(fd); + return false; +#else + delete fileEngine; + fileEngine = 0; + QFSFileEngine *fe = new QFSFileEngine; + fileEngine = fe; + return fe->open(QIODevice::OpenMode(flags), fd, handleFlags); +#endif +} + +bool +QFilePrivate::openExternalFile(int flags, FILE *fh, QFile::FileHandleFlags handleFlags) +{ +#ifdef QT_NO_FSFILEENGINE + Q_UNUSED(flags); + Q_UNUSED(fh); + return false; +#else + delete fileEngine; + fileEngine = 0; + QFSFileEngine *fe = new QFSFileEngine; + fileEngine = fe; + return fe->open(QIODevice::OpenMode(flags), fh, handleFlags); +#endif +} + +#ifdef Q_OS_SYMBIAN +bool QFilePrivate::openExternalFile(int flags, const RFile &f, QFile::FileHandleFlags handleFlags) +{ +#ifdef QT_NO_FSFILEENGINE + Q_UNUSED(flags); + Q_UNUSED(fh); + return false; +#else + delete fileEngine; + fileEngine = 0; + QFSFileEngine *fe = new QFSFileEngine; + fileEngine = fe; + return fe->open(QIODevice::OpenMode(flags), f, handleFlags); +#endif +} +#endif + +inline bool QFilePrivate::ensureFlushed() const +{ + // This function ensures that the write buffer has been flushed (const + // because certain const functions need to call it. + if (lastWasWrite) { + const_cast<QFilePrivate *>(this)->lastWasWrite = false; + if (!const_cast<QFile *>(q_func())->flush()) + return false; + } + return true; +} + +void +QFilePrivate::setError(QFile::FileError err) +{ + error = err; + errorString.clear(); +} + +void +QFilePrivate::setError(QFile::FileError err, const QString &errStr) +{ + error = err; + errorString = errStr; +} + +void +QFilePrivate::setError(QFile::FileError err, int errNum) +{ + error = err; + errorString = qt_error_string(errNum); +} + +//************* QFile + +/*! + \class QFile + \brief The QFile class provides an interface for reading from and writing to files. + + \ingroup io + + \reentrant + + QFile is an I/O device for reading and writing text and binary + files and \l{The Qt Resource System}{resources}. A QFile may be + used by itself or, more conveniently, with a QTextStream or + QDataStream. + + The file name is usually passed in the constructor, but it can be + set at any time using setFileName(). QFile expects the file + separator to be '/' regardless of operating system. The use of + other separators (e.g., '\\') is not supported. + + You can check for a file's existence using exists(), and remove a + file using remove(). (More advanced file system related operations + are provided by QFileInfo and QDir.) + + The file is opened with open(), closed with close(), and flushed + with flush(). Data is usually read and written using QDataStream + or QTextStream, but you can also call the QIODevice-inherited + functions read(), readLine(), readAll(), write(). QFile also + inherits getChar(), putChar(), and ungetChar(), which work one + character at a time. + + The size of the file is returned by size(). You can get the + current file position using pos(), or move to a new file position + using seek(). If you've reached the end of the file, atEnd() + returns true. + + \section1 Reading Files Directly + + The following example reads a text file line by line: + + \snippet doc/src/snippets/file/file.cpp 0 + + The QIODevice::Text flag passed to open() tells Qt to convert + Windows-style line terminators ("\\r\\n") into C++-style + terminators ("\\n"). By default, QFile assumes binary, i.e. it + doesn't perform any conversion on the bytes stored in the file. + + \section1 Using Streams to Read Files + + The next example uses QTextStream to read a text file + line by line: + + \snippet doc/src/snippets/file/file.cpp 1 + + QTextStream takes care of converting the 8-bit data stored on + disk into a 16-bit Unicode QString. By default, it assumes that + the user system's local 8-bit encoding is used (e.g., ISO 8859-1 + for most of Europe; see QTextCodec::codecForLocale() for + details). This can be changed using setCodec(). + + To write text, we can use operator<<(), which is overloaded to + take a QTextStream on the left and various data types (including + QString) on the right: + + \snippet doc/src/snippets/file/file.cpp 2 + + QDataStream is similar, in that you can use operator<<() to write + data and operator>>() to read it back. See the class + documentation for details. + + When you use QFile, QFileInfo, and QDir to access the file system + with Qt, you can use Unicode file names. On Unix, these file + names are converted to an 8-bit encoding. If you want to use + standard C++ APIs (\c <cstdio> or \c <iostream>) or + platform-specific APIs to access files instead of QFile, you can + use the encodeName() and decodeName() functions to convert + between Unicode file names and 8-bit file names. + + On Unix, there are some special system files (e.g. in \c /proc) for which + size() will always return 0, yet you may still be able to read more data + from such a file; the data is generated in direct response to you calling + read(). In this case, however, you cannot use atEnd() to determine if + there is more data to read (since atEnd() will return true for a file that + claims to have size 0). Instead, you should either call readAll(), or call + read() or readLine() repeatedly until no more data can be read. The next + example uses QTextStream to read \c /proc/modules line by line: + + \snippet doc/src/snippets/file/file.cpp 3 + + \section1 Signals + + Unlike other QIODevice implementations, such as QTcpSocket, QFile does not + emit the aboutToClose(), bytesWritten(), or readyRead() signals. This + implementation detail means that QFile is not suitable for reading and + writing certain types of files, such as device files on Unix platforms. + + \section1 Platform Specific Issues + + File permissions are handled differently on Linux/Mac OS X and + Windows. In a non \l{QIODevice::isWritable()}{writable} + directory on Linux, files cannot be created. This is not always + the case on Windows, where, for instance, the 'My Documents' + directory usually is not writable, but it is still possible to + create files in it. + + \sa QTextStream, QDataStream, QFileInfo, QDir, {The Qt Resource System} +*/ + +/*! + \enum QFile::FileError + + This enum describes the errors that may be returned by the error() + function. + + \value NoError No error occurred. + \value ReadError An error occurred when reading from the file. + \value WriteError An error occurred when writing to the file. + \value FatalError A fatal error occurred. + \value ResourceError + \value OpenError The file could not be opened. + \value AbortError The operation was aborted. + \value TimeOutError A timeout occurred. + \value UnspecifiedError An unspecified error occurred. + \value RemoveError The file could not be removed. + \value RenameError The file could not be renamed. + \value PositionError The position in the file could not be changed. + \value ResizeError The file could not be resized. + \value PermissionsError The file could not be accessed. + \value CopyError The file could not be copied. + + \omitvalue ConnectError +*/ + +/*! + \enum QFile::Permission + + This enum is used by the permission() function to report the + permissions and ownership of a file. The values may be OR-ed + together to test multiple permissions and ownership values. + + \value ReadOwner The file is readable by the owner of the file. + \value WriteOwner The file is writable by the owner of the file. + \value ExeOwner The file is executable by the owner of the file. + \value ReadUser The file is readable by the user. + \value WriteUser The file is writable by the user. + \value ExeUser The file is executable by the user. + \value ReadGroup The file is readable by the group. + \value WriteGroup The file is writable by the group. + \value ExeGroup The file is executable by the group. + \value ReadOther The file is readable by anyone. + \value WriteOther The file is writable by anyone. + \value ExeOther The file is executable by anyone. + + \warning Because of differences in the platforms supported by Qt, + the semantics of ReadUser, WriteUser and ExeUser are + platform-dependent: On Unix, the rights of the owner of the file + are returned and on Windows the rights of the current user are + returned. This behavior might change in a future Qt version. + + Note that Qt does not by default check for permissions on NTFS + file systems, as this may decrease the performance of file + handling considerably. It is possible to force permission checking + on NTFS by including the following code in your source: + + \snippet doc/src/snippets/ntfsp.cpp 0 + + Permission checking is then turned on and off by incrementing and + decrementing \c qt_ntfs_permission_lookup by 1. + + \snippet doc/src/snippets/ntfsp.cpp 1 +*/ + +/*! + \enum QFile::FileHandleFlag + + This enum is used when opening a file to specify additional + options which only apply to files and not to a generic + QIODevice. + + \value AutoCloseHandle The file handle passed into open() should be + closed by close(), the default behaviour is that close just flushes + the file and the app is responsible for closing the file handle. When + opening a file by name, this flag is ignored as Qt always "owns" the + file handle and must close it. + */ + +#ifdef QT3_SUPPORT +/*! + \typedef QFile::PermissionSpec + + Use QFile::Permission instead. +*/ +#endif + +#ifdef QT_NO_QOBJECT +QFile::QFile() + : QIODevice(*new QFilePrivate) +{ +} +QFile::QFile(const QString &name) + : QIODevice(*new QFilePrivate) +{ + d_func()->fileName = name; +} +QFile::QFile(QFilePrivate &dd) + : QIODevice(dd) +{ +} +#else +/*! + \internal +*/ +QFile::QFile() + : QIODevice(*new QFilePrivate, 0) +{ +} +/*! + Constructs a new file object with the given \a parent. +*/ +QFile::QFile(QObject *parent) + : QIODevice(*new QFilePrivate, parent) +{ +} +/*! + Constructs a new file object to represent the file with the given \a name. +*/ +QFile::QFile(const QString &name) + : QIODevice(*new QFilePrivate, 0) +{ + Q_D(QFile); + d->fileName = name; +} +/*! + Constructs a new file object with the given \a parent to represent the + file with the specified \a name. +*/ +QFile::QFile(const QString &name, QObject *parent) + : QIODevice(*new QFilePrivate, parent) +{ + Q_D(QFile); + d->fileName = name; +} +/*! + \internal +*/ +QFile::QFile(QFilePrivate &dd, QObject *parent) + : QIODevice(dd, parent) +{ +} +#endif + +/*! + Destroys the file object, closing it if necessary. +*/ +QFile::~QFile() +{ + close(); +} + +/*! + Returns the name set by setFileName() or to the QFile + constructors. + + \sa setFileName(), QFileInfo::fileName() +*/ +QString QFile::fileName() const +{ + return fileEngine()->fileName(QAbstractFileEngine::DefaultName); +} + +/*! + Sets the \a name of the file. The name can have no path, a + relative path, or an absolute path. + + Do not call this function if the file has already been opened. + + If the file name has no path or a relative path, the path used + will be the application's current directory path + \e{at the time of the open()} call. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfile.cpp 0 + + Note that the directory separator "/" works for all operating + systems supported by Qt. + + \sa fileName(), QFileInfo, QDir +*/ +void +QFile::setFileName(const QString &name) +{ + Q_D(QFile); + if (isOpen()) { + qWarning("QFile::setFileName: File (%s) is already opened", + qPrintable(fileName())); + close(); + } + if(d->fileEngine) { //get a new file engine later + delete d->fileEngine; + d->fileEngine = 0; + } + d->fileName = name; +} + +/*! + \fn QString QFile::decodeName(const char *localFileName) + + \overload + + Returns the Unicode version of the given \a localFileName. See + encodeName() for details. +*/ + +/*! + By default, this function converts \a fileName to the local 8-bit + encoding determined by the user's locale. This is sufficient for + file names that the user chooses. File names hard-coded into the + application should only use 7-bit ASCII filename characters. + + \sa decodeName() setEncodingFunction() +*/ + +QByteArray +QFile::encodeName(const QString &fileName) +{ + return (*QFilePrivate::encoder)(fileName); +} + +/*! + \typedef QFile::EncoderFn + + This is a typedef for a pointer to a function with the following + signature: + + \snippet doc/src/snippets/code/src_corelib_io_qfile.cpp 1 + + \sa setEncodingFunction(), encodeName() +*/ + +/*! + This does the reverse of QFile::encodeName() using \a localFileName. + + \sa setDecodingFunction(), encodeName() +*/ + +QString +QFile::decodeName(const QByteArray &localFileName) +{ + return (*QFilePrivate::decoder)(localFileName); +} + +/*! + \fn void QFile::setEncodingFunction(EncoderFn function) + + \nonreentrant + + Sets the \a function for encoding Unicode file names. The + default encodes in the locale-specific 8-bit encoding. + + \sa encodeName(), setDecodingFunction() +*/ + +void +QFile::setEncodingFunction(EncoderFn f) +{ + if (!f) + f = locale_encode; + QFilePrivate::encoder = f; +} + +/*! + \typedef QFile::DecoderFn + + This is a typedef for a pointer to a function with the following + signature: + + \snippet doc/src/snippets/code/src_corelib_io_qfile.cpp 2 + + \sa setDecodingFunction() +*/ + +/*! + \fn void QFile::setDecodingFunction(DecoderFn function) + + \nonreentrant + + Sets the \a function for decoding 8-bit file names. The + default uses the locale-specific 8-bit encoding. + + \sa setEncodingFunction(), decodeName() +*/ + +void +QFile::setDecodingFunction(DecoderFn f) +{ + if (!f) + f = locale_decode; + QFilePrivate::decoder = f; +} + +/*! + \overload + + Returns true if the file specified by fileName() exists; otherwise + returns false. + + \sa fileName(), setFileName() +*/ + +bool +QFile::exists() const +{ + // 0x1000000 = QAbstractFileEngine::Refresh, forcing an update + return (fileEngine()->fileFlags(QAbstractFileEngine::FlagsMask + | QAbstractFileEngine::FileFlag(0x1000000)) & QAbstractFileEngine::ExistsFlag); +} + +/*! + Returns true if the file specified by \a fileName exists; otherwise + returns false. +*/ + +bool +QFile::exists(const QString &fileName) +{ + return QFileInfo(fileName).exists(); +} + +/*! + \fn QString QFile::symLinkTarget() const + \since 4.2 + \overload + + Returns the absolute path of the file or directory a symlink (or shortcut + on Windows) points to, or a an empty string if the object isn't a symbolic + link. + + This name may not represent an existing file; it is only a string. + QFile::exists() returns true if the symlink points to an existing file. + + \sa fileName() setFileName() +*/ + +/*! + \obsolete + + Use symLinkTarget() instead. +*/ +QString +QFile::readLink() const +{ + return fileEngine()->fileName(QAbstractFileEngine::LinkName); +} + +/*! + \fn static QString QFile::symLinkTarget(const QString &fileName) + \since 4.2 + + Returns the absolute path of the file or directory referred to by the + symlink (or shortcut on Windows) specified by \a fileName, or returns an + empty string if the \a fileName does not correspond to a symbolic link. + + This name may not represent an existing file; it is only a string. + QFile::exists() returns true if the symlink points to an existing file. +*/ + +/*! + \obsolete + + Use symLinkTarget() instead. +*/ +QString +QFile::readLink(const QString &fileName) +{ + return QFileInfo(fileName).readLink(); +} + +/*! + Removes the file specified by fileName(). Returns true if successful; + otherwise returns false. + + The file is closed before it is removed. + + \sa setFileName() +*/ + +bool +QFile::remove() +{ + Q_D(QFile); + if (d->fileName.isEmpty()) { + qWarning("QFile::remove: Empty or null file name"); + return false; + } + unsetError(); + close(); + if(error() == QFile::NoError) { + if(fileEngine()->remove()) { + unsetError(); + return true; + } + d->setError(QFile::RemoveError, d->fileEngine->errorString()); + } + return false; +} + +/*! + \overload + + Removes the file specified by the \a fileName given. + + Returns true if successful; otherwise returns false. + + \sa remove() +*/ + +bool +QFile::remove(const QString &fileName) +{ + return QFile(fileName).remove(); +} + +/*! + Renames the file currently specified by fileName() to \a newName. + Returns true if successful; otherwise returns false. + + If a file with the name \a newName already exists, rename() returns false + (i.e., QFile will not overwrite it). + + The file is closed before it is renamed. + + \sa setFileName() +*/ + +bool +QFile::rename(const QString &newName) +{ + Q_D(QFile); + if (d->fileName.isEmpty()) { + qWarning("QFile::rename: Empty or null file name"); + return false; + } + if (QFile(newName).exists()) { + // ### Race condition. If a file is moved in after this, it /will/ be + // overwritten. On Unix, the proper solution is to use hardlinks: + // return ::link(old, new) && ::remove(old); + d->setError(QFile::RenameError, tr("Destination file exists")); + return false; + } + unsetError(); + close(); + if(error() == QFile::NoError) { + if (fileEngine()->rename(newName)) { + unsetError(); + // engine was able to handle the new name so we just reset it + d->fileEngine->setFileName(newName); + d->fileName = newName; + return true; + } + + if (isSequential()) { + d->setError(QFile::RenameError, tr("Will not rename sequential file using block copy")); + return false; + } + + QFile out(newName); + if (open(QIODevice::ReadOnly)) { + if (out.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + bool error = false; + char block[4096]; + qint64 bytes; + while ((bytes = read(block, sizeof(block))) > 0) { + if (bytes != out.write(block, bytes)) { + d->setError(QFile::RenameError, out.errorString()); + error = true; + break; + } + } + if (bytes == -1) { + d->setError(QFile::RenameError, errorString()); + error = true; + } + if(!error) { + if (!remove()) { + d->setError(QFile::RenameError, tr("Cannot remove source file")); + error = true; + } + } + if (error) { + out.remove(); + } else { + d->fileEngine->setFileName(newName); + setPermissions(permissions()); + unsetError(); + setFileName(newName); + } + close(); + return !error; + } + close(); + } + d->setError(QFile::RenameError, out.isOpen() ? errorString() : out.errorString()); + } + return false; +} + +/*! + \overload + + Renames the file \a oldName to \a newName. Returns true if + successful; otherwise returns false. + + If a file with the name \a newName already exists, rename() returns false + (i.e., QFile will not overwrite it). + + \sa rename() +*/ + +bool +QFile::rename(const QString &oldName, const QString &newName) +{ + return QFile(oldName).rename(newName); +} + +/*! + + Creates a link named \a linkName that points to the file currently specified by + fileName(). What a link is depends on the underlying filesystem (be it a + shortcut on Windows or a symbolic link on Unix). Returns true if successful; + otherwise returns false. + + This function will not overwrite an already existing entity in the file system; + in this case, \c link() will return false and set \l{QFile::}{error()} to + return \l{QFile::}{RenameError}. + + \note To create a valid link on Windows, \a linkName must have a \c{.lnk} file extension. + + \note On Symbian, no link is created and false is returned if fileName() + currently specifies a directory. + + \sa setFileName() +*/ + +bool +QFile::link(const QString &linkName) +{ + Q_D(QFile); + if (d->fileName.isEmpty()) { + qWarning("QFile::link: Empty or null file name"); + return false; + } + QFileInfo fi(linkName); + if(fileEngine()->link(fi.absoluteFilePath())) { + unsetError(); + return true; + } + d->setError(QFile::RenameError, d->fileEngine->errorString()); + return false; +} + +/*! + \overload + + Creates a link named \a linkName that points to the file \a fileName. What a link is + depends on the underlying filesystem (be it a shortcut on Windows + or a symbolic link on Unix). Returns true if successful; otherwise + returns false. + + \sa link() +*/ + +bool +QFile::link(const QString &fileName, const QString &linkName) +{ + return QFile(fileName).link(linkName); +} + +/*! + Copies the file currently specified by fileName() to a file called + \a newName. Returns true if successful; otherwise returns false. + + Note that if a file with the name \a newName already exists, + copy() returns false (i.e. QFile will not overwrite it). + + The source file is closed before it is copied. + + \sa setFileName() +*/ + +bool +QFile::copy(const QString &newName) +{ + Q_D(QFile); + if (d->fileName.isEmpty()) { + qWarning("QFile::copy: Empty or null file name"); + return false; + } + if (QFile(newName).exists()) { + // ### Race condition. If a file is moved in after this, it /will/ be + // overwritten. On Unix, the proper solution is to use hardlinks: + // return ::link(old, new) && ::remove(old); See also rename(). + d->setError(QFile::CopyError, tr("Destination file exists")); + return false; + } + unsetError(); + close(); + if(error() == QFile::NoError) { + if(fileEngine()->copy(newName)) { + unsetError(); + return true; + } else { + bool error = false; + if(!open(QFile::ReadOnly)) { + error = true; + d->setError(QFile::CopyError, tr("Cannot open %1 for input").arg(d->fileName)); + } else { + QString fileTemplate = QLatin1String("%1/qt_temp.XXXXXX"); +#ifdef QT_NO_TEMPORARYFILE + QFile out(fileTemplate.arg(QFileInfo(newName).path())); + if (!out.open(QIODevice::ReadWrite)) + error = true; +#else + QTemporaryFile out(fileTemplate.arg(QFileInfo(newName).path())); + if (!out.open()) { + out.setFileTemplate(fileTemplate.arg(QDir::tempPath())); + if (!out.open()) + error = true; + } +#endif + if (error) { + out.close(); + d->setError(QFile::CopyError, tr("Cannot open for output")); + } else { + char block[4096]; + qint64 totalRead = 0; + while(!atEnd()) { + qint64 in = read(block, sizeof(block)); + if (in <= 0) + break; + totalRead += in; + if(in != out.write(block, in)) { + d->setError(QFile::CopyError, tr("Failure to write block")); + error = true; + break; + } + } + + if (totalRead != size()) { + // Unable to read from the source. The error string is + // already set from read(). + error = true; + } + if (!error && !out.rename(newName)) { + error = true; + d->setError(QFile::CopyError, tr("Cannot create %1 for output").arg(newName)); + } +#ifdef QT_NO_TEMPORARYFILE + if (error) + out.remove(); +#else + if (!error) + out.setAutoRemove(false); +#endif + } + close(); + } + if(!error) { + QFile::setPermissions(newName, permissions()); + unsetError(); + return true; + } + } + } + return false; +} + +/*! + \overload + + Copies the file \a fileName to \a newName. Returns true if successful; + otherwise returns false. + + If a file with the name \a newName already exists, copy() returns false + (i.e., QFile will not overwrite it). + + \sa rename() +*/ + +bool +QFile::copy(const QString &fileName, const QString &newName) +{ + return QFile(fileName).copy(newName); +} + +/*! + Returns true if the file can only be manipulated sequentially; + otherwise returns false. + + Most files support random-access, but some special files may not. + + \sa QIODevice::isSequential() +*/ +bool QFile::isSequential() const +{ + Q_D(const QFile); + return d->fileEngine && d->fileEngine->isSequential(); +} + +/*! + Opens the file using OpenMode \a mode, returning true if successful; + otherwise false. + + The \a mode must be QIODevice::ReadOnly, QIODevice::WriteOnly, or + QIODevice::ReadWrite. It may also have additional flags, such as + QIODevice::Text and QIODevice::Unbuffered. + + \note In \l{QIODevice::}{WriteOnly} or \l{QIODevice::}{ReadWrite} + mode, if the relevant file does not already exist, this function + will try to create a new file before opening it. + + \sa QIODevice::OpenMode, setFileName() +*/ +bool QFile::open(OpenMode mode) +{ + Q_D(QFile); + if (isOpen()) { + qWarning("QFile::open: File (%s) already open", qPrintable(fileName())); + return false; + } + if (mode & Append) + mode |= WriteOnly; + + unsetError(); + if ((mode & (ReadOnly | WriteOnly)) == 0) { + qWarning("QIODevice::open: File access not specified"); + return false; + } + +#ifdef Q_OS_SYMBIAN + // For symbian, the unbuffered flag is used to control write-behind cache behaviour + if (fileEngine()->open(mode)) +#else + // QIODevice provides the buffering, so there's no need to request it from the file engine. + if (fileEngine()->open(mode | QIODevice::Unbuffered)) +#endif + { + QIODevice::open(mode); + if (mode & Append) + seek(size()); + return true; + } + QFile::FileError err = d->fileEngine->error(); + if(err == QFile::UnspecifiedError) + err = QFile::OpenError; + d->setError(err, d->fileEngine->errorString()); + return false; +} + +/*! \fn QFile::open(OpenMode, FILE*) + + Use open(FILE *, OpenMode) instead. +*/ + +/*! + \overload + + Opens the existing file handle \a fh in the given \a mode. + Returns true if successful; otherwise returns false. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfile.cpp 3 + + When a QFile is opened using this function, close() does not actually + close the file, but only flushes it. + + \bold{Warning:} + \list 1 + \o If \a fh does not refer to a regular file, e.g., it is \c stdin, + \c stdout, or \c stderr, you may not be able to seek(). size() + returns \c 0 in those cases. See QIODevice::isSequential() for + more information. + \o Since this function opens the file without specifying the file name, + you cannot use this QFile with a QFileInfo. + \endlist + + \note For Windows CE you may not be able to call resize(). + + \sa close(), {qmake Variable Reference#CONFIG}{qmake Variable Reference} + + \bold{Note for the Windows Platform} + + \a fh must be opened in binary mode (i.e., the mode string must contain + 'b', as in "rb" or "wb") when accessing files and other random-access + devices. Qt will translate the end-of-line characters if you pass + QIODevice::Text to \a mode. Sequential devices, such as stdin and stdout, + are unaffected by this limitation. + + You need to enable support for console applications in order to use the + stdin, stdout and stderr streams at the console. To do this, add the + following declaration to your application's project file: + + \snippet doc/src/snippets/code/src_corelib_io_qfile.cpp 4 +*/ +// ### Qt5: merge this into new overload with a default parameter +bool QFile::open(FILE *fh, OpenMode mode) +{ + return open(fh, mode, DontCloseHandle); +} + +/*! + \overload + + Opens the existing file handle \a fh in the given \a mode. + Returns true if successful; otherwise returns false. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfile.cpp 3 + + When a QFile is opened using this function, behaviour of close() is + controlled by the AutoCloseHandle flag. + If AutoCloseHandle is specified, and this function succeeds, + then calling close() closes the adopted handle. + Otherwise, close() does not actually close the file, but only flushes it. + + \bold{Warning:} + \list 1 + \o If \a fh does not refer to a regular file, e.g., it is \c stdin, + \c stdout, or \c stderr, you may not be able to seek(). size() + returns \c 0 in those cases. See QIODevice::isSequential() for + more information. + \o Since this function opens the file without specifying the file name, + you cannot use this QFile with a QFileInfo. + \endlist + + \note For Windows CE you may not be able to call resize(). + + \sa close(), {qmake Variable Reference#CONFIG}{qmake Variable Reference} + + \bold{Note for the Windows Platform} + + \a fh must be opened in binary mode (i.e., the mode string must contain + 'b', as in "rb" or "wb") when accessing files and other random-access + devices. Qt will translate the end-of-line characters if you pass + QIODevice::Text to \a mode. Sequential devices, such as stdin and stdout, + are unaffected by this limitation. + + You need to enable support for console applications in order to use the + stdin, stdout and stderr streams at the console. To do this, add the + following declaration to your application's project file: + + \snippet doc/src/snippets/code/src_corelib_io_qfile.cpp 4 +*/ +bool QFile::open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags) +{ + Q_D(QFile); + if (isOpen()) { + qWarning("QFile::open: File (%s) already open", qPrintable(fileName())); + return false; + } + if (mode & Append) + mode |= WriteOnly; + unsetError(); + if ((mode & (ReadOnly | WriteOnly)) == 0) { + qWarning("QFile::open: File access not specified"); + return false; + } + if (d->openExternalFile(mode, fh, handleFlags)) { + QIODevice::open(mode); + if (mode & Append) { + seek(size()); + } else { + qint64 pos = (qint64)QT_FTELL(fh); + if (pos != -1) + seek(pos); + } + return true; + } + return false; +} + +/*! \fn QFile::open(OpenMode, int) + + Use open(int, OpenMode) instead. +*/ + +/*! + \overload + + Opens the existing file descriptor \a fd in the given \a mode. + Returns true if successful; otherwise returns false. + + When a QFile is opened using this function, close() does not + actually close the file. + + The QFile that is opened using this function is automatically set + to be in raw mode; this means that the file input/output functions + are slow. If you run into performance issues, you should try to + use one of the other open functions. + + \warning If \a fd is not a regular file, e.g, it is 0 (\c stdin), + 1 (\c stdout), or 2 (\c stderr), you may not be able to seek(). In + those cases, size() returns \c 0. See QIODevice::isSequential() + for more information. + + \warning For Windows CE you may not be able to call seek(), setSize(), + fileTime(). size() returns \c 0. + + \warning Since this function opens the file without specifying the file name, + you cannot use this QFile with a QFileInfo. + + \sa close() +*/ +// ### Qt5: merge this into new overload with a default parameter +bool QFile::open(int fd, OpenMode mode) +{ + return open(fd, mode, DontCloseHandle); +} + +/*! + \overload + + Opens the existing file descriptor \a fd in the given \a mode. + Returns true if successful; otherwise returns false. + + When a QFile is opened using this function, behaviour of close() is + controlled by the AutoCloseHandle flag. + If AutoCloseHandle is specified, and this function succeeds, + then calling close() closes the adopted handle. + Otherwise, close() does not actually close the file, but only flushes it. + + The QFile that is opened using this function is automatically set + to be in raw mode; this means that the file input/output functions + are slow. If you run into performance issues, you should try to + use one of the other open functions. + + \warning If \a fd is not a regular file, e.g, it is 0 (\c stdin), + 1 (\c stdout), or 2 (\c stderr), you may not be able to seek(). In + those cases, size() returns \c 0. See QIODevice::isSequential() + for more information. + + \warning For Windows CE you may not be able to call seek(), setSize(), + fileTime(). size() returns \c 0. + + \warning Since this function opens the file without specifying the file name, + you cannot use this QFile with a QFileInfo. + + \sa close() +*/ +bool QFile::open(int fd, OpenMode mode, FileHandleFlags handleFlags) +{ + Q_D(QFile); + if (isOpen()) { + qWarning("QFile::open: File (%s) already open", qPrintable(fileName())); + return false; + } + if (mode & Append) + mode |= WriteOnly; + unsetError(); + if ((mode & (ReadOnly | WriteOnly)) == 0) { + qWarning("QFile::open: File access not specified"); + return false; + } + if (d->openExternalFile(mode, fd, handleFlags)) { + QIODevice::open(mode); + if (mode & Append) { + seek(size()); + } else { + qint64 pos = (qint64)QT_LSEEK(fd, QT_OFF_T(0), SEEK_CUR); + if (pos != -1) + seek(pos); + } + return true; + } + return false; +} + +#ifdef Q_OS_SYMBIAN +/*! + \overload + + Opens the existing file object \a f in the given \a mode. + Returns true if successful; otherwise returns false. + + When a QFile is opened using this function, behaviour of close() is + controlled by the AutoCloseHandle flag. + If AutoCloseHandle is specified, and this function succeeds, + then calling close() closes the adopted handle. + Otherwise, close() does not actually close the file, but only flushes it. + + \warning If the file handle is adopted from another process, + you may not be able to use this QFile with a QFileInfo. + + \sa close() +*/ +bool QFile::open(const RFile &f, OpenMode mode, FileHandleFlags handleFlags) +{ + Q_D(QFile); + if (isOpen()) { + qWarning("QFile::open: File (%s) already open", qPrintable(fileName())); + return false; + } + if (mode & Append) + mode |= WriteOnly; + unsetError(); + if ((mode & (ReadOnly | WriteOnly)) == 0) { + qWarning("QFile::open: File access not specified"); + return false; + } + if (d->openExternalFile(mode, f, handleFlags)) { + bool ok = QIODevice::open(mode); + if (ok) { + if (mode & Append) { + ok = seek(size()); + } else { + qint64 pos = 0; + TInt err; +#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API + err = static_cast<const RFile64&>(f).Seek(ESeekCurrent, pos); +#else + TInt pos32 = 0; + err = f.Seek(ESeekCurrent, pos32); + pos = pos32; +#endif + ok = ok && (err == KErrNone); + ok = ok && seek(pos); + } + } + return ok; + } + return false; +} +#endif + +/*! + Returns the file handle of the file. + + This is a small positive integer, suitable for use with C library + functions such as fdopen() and fcntl(). On systems that use file + descriptors for sockets (i.e. Unix systems, but not Windows) the handle + can be used with QSocketNotifier as well. + + If the file is not open, or there is an error, handle() returns -1. + + This function is not supported on Windows CE. + + \sa QSocketNotifier +*/ + +int +QFile::handle() const +{ + Q_D(const QFile); + if (!isOpen() || !d->fileEngine) + return -1; + + return d->fileEngine->handle(); +} + +/*! + \enum QFile::MemoryMapFlags + \since 4.4 + + This enum describes special options that may be used by the map() + function. + + \value NoOptions No options. +*/ + +/*! + \since 4.4 + Maps \a size bytes of the file into memory starting at \a offset. A file + should be open for a map to succeed but the file does not need to stay + open after the memory has been mapped. When the QFile is destroyed + or a new file is opened with this object, any maps that have not been + unmapped will automatically be unmapped. + + Any mapping options can be passed through \a flags. + + Returns a pointer to the memory or 0 if there is an error. + + \note On Windows CE 5.0 the file will be closed before mapping occurs. + + \sa unmap(), QAbstractFileEngine::supportsExtension() + */ +uchar *QFile::map(qint64 offset, qint64 size, MemoryMapFlags flags) +{ + Q_D(QFile); + if (fileEngine() + && d->fileEngine->supportsExtension(QAbstractFileEngine::MapExtension)) { + unsetError(); + uchar *address = d->fileEngine->map(offset, size, flags); + if (address == 0) + d->setError(d->fileEngine->error(), d->fileEngine->errorString()); + return address; + } + return 0; +} + +/*! + \since 4.4 + Unmaps the memory \a address. + + Returns true if the unmap succeeds; false otherwise. + + \sa map(), QAbstractFileEngine::supportsExtension() + */ +bool QFile::unmap(uchar *address) +{ + Q_D(QFile); + if (fileEngine() + && d->fileEngine->supportsExtension(QAbstractFileEngine::UnMapExtension)) { + unsetError(); + bool success = d->fileEngine->unmap(address); + if (!success) + d->setError(d->fileEngine->error(), d->fileEngine->errorString()); + return success; + } + d->setError(PermissionsError, tr("No file engine available or engine does not support UnMapExtension")); + return false; +} + +/*! + \fn QString QFile::name() const + + Use fileName() instead. +*/ + +/*! + \fn void QFile::setName(const QString &name) + + Use setFileName() instead. +*/ + +/*! + Sets the file size (in bytes) \a sz. Returns true if the file if the + resize succeeds; false otherwise. If \a sz is larger than the file + currently is the new bytes will be set to 0, if \a sz is smaller the + file is simply truncated. + + \sa size(), setFileName() +*/ + +bool +QFile::resize(qint64 sz) +{ + Q_D(QFile); + if (!d->ensureFlushed()) + return false; + fileEngine(); + if (isOpen() && d->fileEngine->pos() > sz) + seek(sz); + if(d->fileEngine->setSize(sz)) { + unsetError(); + d->cachedSize = sz; + return true; + } + d->cachedSize = 0; + d->setError(QFile::ResizeError, d->fileEngine->errorString()); + return false; +} + +/*! + \overload + + Sets \a fileName to size (in bytes) \a sz. Returns true if the file if + the resize succeeds; false otherwise. If \a sz is larger than \a + fileName currently is the new bytes will be set to 0, if \a sz is + smaller the file is simply truncated. + + \sa resize() +*/ + +bool +QFile::resize(const QString &fileName, qint64 sz) +{ + return QFile(fileName).resize(sz); +} + +/*! + Returns the complete OR-ed together combination of + QFile::Permission for the file. + + \sa setPermissions(), setFileName() +*/ + +QFile::Permissions +QFile::permissions() const +{ + QAbstractFileEngine::FileFlags perms = fileEngine()->fileFlags(QAbstractFileEngine::PermsMask) & QAbstractFileEngine::PermsMask; + return QFile::Permissions((int)perms); //ewww +} + +/*! + \overload + + Returns the complete OR-ed together combination of + QFile::Permission for \a fileName. +*/ + +QFile::Permissions +QFile::permissions(const QString &fileName) +{ + return QFile(fileName).permissions(); +} + +/*! + Sets the permissions for the file to the \a permissions specified. + Returns true if successful, or false if the permissions cannot be + modified. + + \sa permissions(), setFileName() +*/ + +bool +QFile::setPermissions(Permissions permissions) +{ + Q_D(QFile); + if(fileEngine()->setPermissions(permissions)) { + unsetError(); + return true; + } + d->setError(QFile::PermissionsError, d->fileEngine->errorString()); + return false; +} + +/*! + \overload + + Sets the permissions for \a fileName file to \a permissions. +*/ + +bool +QFile::setPermissions(const QString &fileName, Permissions permissions) +{ + return QFile(fileName).setPermissions(permissions); +} + +static inline qint64 _qfile_writeData(QAbstractFileEngine *engine, QRingBuffer *buffer) +{ + qint64 ret = engine->write(buffer->readPointer(), buffer->nextDataBlockSize()); + if (ret > 0) + buffer->free(ret); + return ret; +} + +/*! + Flushes any buffered data to the file. Returns true if successful; + otherwise returns false. +*/ + +bool +QFile::flush() +{ + Q_D(QFile); + if (!d->fileEngine) { + qWarning("QFile::flush: No file engine. Is IODevice open?"); + return false; + } + + if (!d->writeBuffer.isEmpty()) { + qint64 size = d->writeBuffer.size(); + if (_qfile_writeData(d->fileEngine, &d->writeBuffer) != size) { + QFile::FileError err = d->fileEngine->error(); + if(err == QFile::UnspecifiedError) + err = QFile::WriteError; + d->setError(err, d->fileEngine->errorString()); + return false; + } + } + + if (!d->fileEngine->flush()) { + QFile::FileError err = d->fileEngine->error(); + if(err == QFile::UnspecifiedError) + err = QFile::WriteError; + d->setError(err, d->fileEngine->errorString()); + return false; + } + return true; +} + +/*! + Calls QFile::flush() and closes the file. Errors from flush are ignored. + + \sa QIODevice::close() +*/ +void +QFile::close() +{ + Q_D(QFile); + if(!isOpen()) + return; + bool flushed = flush(); + QIODevice::close(); + + // reset write buffer + d->lastWasWrite = false; + d->writeBuffer.clear(); + + // keep earlier error from flush + if (d->fileEngine->close() && flushed) + unsetError(); + else if (flushed) + d->setError(d->fileEngine->error(), d->fileEngine->errorString()); +} + +/*! + Returns the size of the file. + + For regular empty files on Unix (e.g. those in \c /proc), this function + returns 0; the contents of such a file are generated on demand in response + to you calling read(). +*/ + +qint64 QFile::size() const +{ + Q_D(const QFile); + if (!d->ensureFlushed()) + return 0; + d->cachedSize = fileEngine()->size(); + return d->cachedSize; +} + +/*! + \reimp +*/ + +qint64 QFile::pos() const +{ + return QIODevice::pos(); +} + +/*! + Returns true if the end of the file has been reached; otherwise returns + false. + + For regular empty files on Unix (e.g. those in \c /proc), this function + returns true, since the file system reports that the size of such a file is + 0. Therefore, you should not depend on atEnd() when reading data from such a + file, but rather call read() until no more data can be read. +*/ + +bool QFile::atEnd() const +{ + Q_D(const QFile); + + // If there's buffered data left, we're not at the end. + if (!d->buffer.isEmpty()) + return false; + + if (!isOpen()) + return true; + + if (!d->ensureFlushed()) + return false; + + // If the file engine knows best, say what it says. + if (d->fileEngine->supportsExtension(QAbstractFileEngine::AtEndExtension)) { + // Check if the file engine supports AtEndExtension, and if it does, + // check if the file engine claims to be at the end. + return d->fileEngine->atEnd(); + } + + // if it looks like we are at the end, or if size is not cached, + // fall through to bytesAvailable() to make sure. + if (pos() < d->cachedSize) + return false; + + // Fall back to checking how much is available (will stat files). + return bytesAvailable() == 0; +} + +/*! + For random-access devices, this function sets the current position + to \a pos, returning true on success, or false if an error occurred. + For sequential devices, the default behavior is to do nothing and + return false. + + Seeking beyond the end of a file: + If the position is beyond the end of a file, then seek() shall not + immediately extend the file. If a write is performed at this position, + then the file shall be extended. The content of the file between the + previous end of file and the newly written data is UNDEFINED and + varies between platforms and file systems. +*/ + +bool QFile::seek(qint64 off) +{ + Q_D(QFile); + if (!isOpen()) { + qWarning("QFile::seek: IODevice is not open"); + return false; + } + + if (!d->ensureFlushed()) + return false; + + if (!d->fileEngine->seek(off) || !QIODevice::seek(off)) { + QFile::FileError err = d->fileEngine->error(); + if(err == QFile::UnspecifiedError) + err = QFile::PositionError; + d->setError(err, d->fileEngine->errorString()); + return false; + } + unsetError(); + return true; +} + +/*! + \reimp +*/ +qint64 QFile::readLineData(char *data, qint64 maxlen) +{ + Q_D(QFile); + if (!d->ensureFlushed()) + return -1; + + qint64 read; + if (d->fileEngine->supportsExtension(QAbstractFileEngine::FastReadLineExtension)) { + read = d->fileEngine->readLine(data, maxlen); + } else { + // Fall back to QIODevice's readLine implementation if the engine + // cannot do it faster. + read = QIODevice::readLineData(data, maxlen); + } + + if (read < maxlen) { + // failed to read all requested, may be at the end of file, stop caching size so that it's rechecked + d->cachedSize = 0; + } + + return read; +} + +/*! + \reimp +*/ + +qint64 QFile::readData(char *data, qint64 len) +{ + Q_D(QFile); + unsetError(); + if (!d->ensureFlushed()) + return -1; + + qint64 read = d->fileEngine->read(data, len); + if(read < 0) { + QFile::FileError err = d->fileEngine->error(); + if(err == QFile::UnspecifiedError) + err = QFile::ReadError; + d->setError(err, d->fileEngine->errorString()); + } + + if (read < len) { + // failed to read all requested, may be at the end of file, stop caching size so that it's rechecked + d->cachedSize = 0; + } + + return read; +} + +/*! + \internal +*/ +bool QFilePrivate::putCharHelper(char c) +{ +#ifdef QT_NO_QOBJECT + return QIODevicePrivate::putCharHelper(c); +#else + + // Cutoff for code that doesn't only touch the buffer. + int writeBufferSize = writeBuffer.size(); + if ((openMode & QIODevice::Unbuffered) || writeBufferSize + 1 >= QFILE_WRITEBUFFER_SIZE +#ifdef Q_OS_WIN + || ((openMode & QIODevice::Text) && c == '\n' && writeBufferSize + 2 >= QFILE_WRITEBUFFER_SIZE) +#endif + ) { + return QIODevicePrivate::putCharHelper(c); + } + + if (!(openMode & QIODevice::WriteOnly)) { + if (openMode == QIODevice::NotOpen) + qWarning("QIODevice::putChar: Closed device"); + else + qWarning("QIODevice::putChar: ReadOnly device"); + return false; + } + + // Make sure the device is positioned correctly. + const bool sequential = isSequential(); + if (pos != devicePos && !sequential && !q_func()->seek(pos)) + return false; + + lastWasWrite = true; + + int len = 1; +#ifdef Q_OS_WIN + if ((openMode & QIODevice::Text) && c == '\n') { + ++len; + *writeBuffer.reserve(1) = '\r'; + } +#endif + + // Write to buffer. + *writeBuffer.reserve(1) = c; + + if (!sequential) { + pos += len; + devicePos += len; + if (!buffer.isEmpty()) + buffer.skip(len); + } + + return true; +#endif +} + +/*! + \reimp +*/ + +qint64 +QFile::writeData(const char *data, qint64 len) +{ + Q_D(QFile); + unsetError(); + d->lastWasWrite = true; + bool buffered = !(d->openMode & Unbuffered); + + // Flush buffered data if this read will overflow. + if (buffered && (d->writeBuffer.size() + len) > QFILE_WRITEBUFFER_SIZE) { + if (!flush()) + return -1; + } + + // Write directly to the engine if the block size is larger than + // the write buffer size. + if (!buffered || len > QFILE_WRITEBUFFER_SIZE) { + qint64 ret = d->fileEngine->write(data, len); + if(ret < 0) { + QFile::FileError err = d->fileEngine->error(); + if(err == QFile::UnspecifiedError) + err = QFile::WriteError; + d->setError(err, d->fileEngine->errorString()); + } + return ret; + } + + // Write to the buffer. + char *writePointer = d->writeBuffer.reserve(len); + if (len == 1) + *writePointer = *data; + else + ::memcpy(writePointer, data, len); + return len; +} + +/*! + \internal + Returns the QIOEngine for this QFile object. +*/ +QAbstractFileEngine *QFile::fileEngine() const +{ + Q_D(const QFile); + if(!d->fileEngine) + d->fileEngine = QAbstractFileEngine::create(d->fileName); + return d->fileEngine; +} + +/*! + Returns the file error status. + + The I/O device status returns an error code. For example, if open() + returns false, or a read/write operation returns -1, this function can + be called to find out the reason why the operation failed. + + \sa unsetError() +*/ + +QFile::FileError +QFile::error() const +{ + Q_D(const QFile); + return d->error; +} + +/*! + Sets the file's error to QFile::NoError. + + \sa error() +*/ +void +QFile::unsetError() +{ + Q_D(QFile); + d->setError(QFile::NoError); +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfile.h b/src/corelib/io/qfile.h new file mode 100644 index 0000000000..41835342f4 --- /dev/null +++ b/src/corelib/io/qfile.h @@ -0,0 +1,218 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILE_H +#define QFILE_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qstring.h> +#include <stdio.h> +#ifdef Q_OS_SYMBIAN +#include <f32file.h> +#endif + +#ifdef open +#error qfile.h must be included before any header file that defines open +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QAbstractFileEngine; +class QFilePrivate; + +class Q_CORE_EXPORT QFile : public QIODevice +{ +#ifndef QT_NO_QOBJECT + Q_OBJECT +#endif + Q_DECLARE_PRIVATE(QFile) + +public: + + enum FileError { + NoError = 0, + ReadError = 1, + WriteError = 2, + FatalError = 3, + ResourceError = 4, + OpenError = 5, + AbortError = 6, + TimeOutError = 7, + UnspecifiedError = 8, + RemoveError = 9, + RenameError = 10, + PositionError = 11, + ResizeError = 12, + PermissionsError = 13, + CopyError = 14 +#ifdef QT3_SUPPORT + , ConnectError = 30 +#endif + }; + + enum Permission { + ReadOwner = 0x4000, WriteOwner = 0x2000, ExeOwner = 0x1000, + ReadUser = 0x0400, WriteUser = 0x0200, ExeUser = 0x0100, + ReadGroup = 0x0040, WriteGroup = 0x0020, ExeGroup = 0x0010, + ReadOther = 0x0004, WriteOther = 0x0002, ExeOther = 0x0001 + }; + Q_DECLARE_FLAGS(Permissions, Permission) + + enum FileHandleFlag { + AutoCloseHandle = 0x0001, + DontCloseHandle = 0 + }; + Q_DECLARE_FLAGS(FileHandleFlags, FileHandleFlag) + + QFile(); + QFile(const QString &name); +#ifndef QT_NO_QOBJECT + explicit QFile(QObject *parent); + QFile(const QString &name, QObject *parent); +#endif + ~QFile(); + + FileError error() const; + void unsetError(); + + QString fileName() const; + void setFileName(const QString &name); + + typedef QByteArray (*EncoderFn)(const QString &fileName); + typedef QString (*DecoderFn)(const QByteArray &localfileName); + static QByteArray encodeName(const QString &fileName); + static QString decodeName(const QByteArray &localFileName); + inline static QString decodeName(const char *localFileName) + { return decodeName(QByteArray(localFileName)); } + static void setEncodingFunction(EncoderFn); + static void setDecodingFunction(DecoderFn); + + bool exists() const; + static bool exists(const QString &fileName); + + QString readLink() const; + static QString readLink(const QString &fileName); + inline QString symLinkTarget() const { return readLink(); } + inline static QString symLinkTarget(const QString &fileName) { return readLink(fileName); } + + bool remove(); + static bool remove(const QString &fileName); + + bool rename(const QString &newName); + static bool rename(const QString &oldName, const QString &newName); + + bool link(const QString &newName); + static bool link(const QString &oldname, const QString &newName); + + bool copy(const QString &newName); + static bool copy(const QString &fileName, const QString &newName); + + bool isSequential() const; + + bool open(OpenMode flags); + bool open(FILE *f, OpenMode flags); + bool open(int fd, OpenMode flags); +#ifdef Q_OS_SYMBIAN + bool open(const RFile &f, OpenMode flags, FileHandleFlags handleFlags = DontCloseHandle); +#endif + bool open(FILE *f, OpenMode ioFlags, FileHandleFlags handleFlags); + bool open(int fd, OpenMode ioFlags, FileHandleFlags handleFlags); + virtual void close(); + + qint64 size() const; + qint64 pos() const; + bool seek(qint64 offset); + bool atEnd() const; + bool flush(); + + bool resize(qint64 sz); + static bool resize(const QString &filename, qint64 sz); + + Permissions permissions() const; + static Permissions permissions(const QString &filename); + bool setPermissions(Permissions permissionSpec); + static bool setPermissions(const QString &filename, Permissions permissionSpec); + + int handle() const; + + enum MemoryMapFlags { + NoOptions = 0 + }; + + uchar *map(qint64 offset, qint64 size, MemoryMapFlags flags = NoOptions); + bool unmap(uchar *address); + + virtual QAbstractFileEngine *fileEngine() const; + +#ifdef QT3_SUPPORT + typedef Permission PermissionSpec; + inline QT3_SUPPORT QString name() const { return fileName(); } + inline QT3_SUPPORT void setName(const QString &aName) { setFileName(aName); } + inline QT3_SUPPORT bool open(OpenMode aFlags, FILE *f) { return open(f, aFlags); } + inline QT3_SUPPORT bool open(OpenMode aFlags, int fd) { return open(fd, aFlags); } +#endif + +protected: +#ifdef QT_NO_QOBJECT + QFile(QFilePrivate &dd); +#else + QFile(QFilePrivate &dd, QObject *parent = 0); +#endif + + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + qint64 readLineData(char *data, qint64 maxlen); + +private: + Q_DISABLE_COPY(QFile) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QFile::Permissions) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFILE_H diff --git a/src/corelib/io/qfile_p.h b/src/corelib/io/qfile_p.h new file mode 100644 index 0000000000..d647c958d7 --- /dev/null +++ b/src/corelib/io/qfile_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILE_P_H +#define QFILE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qabstractfileengine.h" +#include "private/qiodevice_p.h" +#include "private/qringbuffer_p.h" + +QT_BEGIN_NAMESPACE + +class QFilePrivate : public QIODevicePrivate +{ + Q_DECLARE_PUBLIC(QFile) + +protected: + QFilePrivate(); + ~QFilePrivate(); + + bool openExternalFile(int flags, int fd, QFile::FileHandleFlags handleFlags); + bool openExternalFile(int flags, FILE *fh, QFile::FileHandleFlags handleFlags); +#ifdef Q_OS_SYMBIAN + bool openExternalFile(int flags, const RFile& f, QFile::FileHandleFlags handleFlags); +#endif + + QString fileName; + mutable QAbstractFileEngine *fileEngine; + + bool lastWasWrite; + QRingBuffer writeBuffer; + inline bool ensureFlushed() const; + + bool putCharHelper(char c); + + QFile::FileError error; + void setError(QFile::FileError err); + void setError(QFile::FileError err, const QString &errorString); + void setError(QFile::FileError err, int errNum); + + mutable qint64 cachedSize; + +private: + static QFile::EncoderFn encoder; + static QFile::DecoderFn decoder; +}; + +QT_END_NAMESPACE + +#endif // QFILE_P_H diff --git a/src/corelib/io/qfileinfo.cpp b/src/corelib/io/qfileinfo.cpp new file mode 100644 index 0000000000..6b9c82c41b --- /dev/null +++ b/src/corelib/io/qfileinfo.cpp @@ -0,0 +1,1399 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qfileinfo.h" +#include "qglobal.h" +#include "qdir.h" +#include "qfileinfo_p.h" + +QT_BEGIN_NAMESPACE + +QString QFileInfoPrivate::getFileName(QAbstractFileEngine::FileName name) const +{ + if (cache_enabled && !fileNames[(int)name].isNull()) + return fileNames[(int)name]; + + QString ret; + if (fileEngine == 0) { // local file; use the QFileSystemEngine directly + switch (name) { + case QAbstractFileEngine::CanonicalName: + case QAbstractFileEngine::CanonicalPathName: { + QFileSystemEntry entry = QFileSystemEngine::canonicalName(fileEntry, metaData); + if (cache_enabled) { // be smart and store both + fileNames[QAbstractFileEngine::CanonicalName] = entry.filePath(); + fileNames[QAbstractFileEngine::CanonicalPathName] = entry.path(); + } + if (name == QAbstractFileEngine::CanonicalName) + ret = entry.filePath(); + else + ret = entry.path(); + break; + } + case QAbstractFileEngine::LinkName: + ret = QFileSystemEngine::getLinkTarget(fileEntry, metaData).filePath(); + break; + case QAbstractFileEngine::BundleName: + ret = QFileSystemEngine::bundleName(fileEntry); + break; + case QAbstractFileEngine::AbsoluteName: + case QAbstractFileEngine::AbsolutePathName: { + QFileSystemEntry entry = QFileSystemEngine::absoluteName(fileEntry); + if (cache_enabled) { // be smart and store both + fileNames[QAbstractFileEngine::AbsoluteName] = entry.filePath(); + fileNames[QAbstractFileEngine::AbsolutePathName] = entry.path(); + } + if (name == QAbstractFileEngine::AbsoluteName) + ret = entry.filePath(); + else + ret = entry.path(); + break; + } + default: break; + } + } else { + ret = fileEngine->fileName(name); + } + if (ret.isNull()) + ret = QLatin1String(""); + if (cache_enabled) + fileNames[(int)name] = ret; + return ret; +} + +QString QFileInfoPrivate::getFileOwner(QAbstractFileEngine::FileOwner own) const +{ + if (cache_enabled && !fileOwners[(int)own].isNull()) + return fileOwners[(int)own]; + QString ret; + if (fileEngine == 0) { + switch (own) { + case QAbstractFileEngine::OwnerUser: + ret = QFileSystemEngine::resolveUserName(fileEntry, metaData); + break; + case QAbstractFileEngine::OwnerGroup: + ret = QFileSystemEngine::resolveGroupName(fileEntry, metaData); + break; + } + } else { + ret = fileEngine->owner(own); + } + if (ret.isNull()) + ret = QLatin1String(""); + if (cache_enabled) + fileOwners[(int)own] = ret; + return ret; +} + +uint QFileInfoPrivate::getFileFlags(QAbstractFileEngine::FileFlags request) const +{ + Q_ASSERT(fileEngine); // should never be called when using the native FS + // We split the testing into tests for for LinkType, BundleType, PermsMask + // and the rest. + // Tests for file permissions on Windows can be slow, expecially on network + // paths and NTFS drives. + // In order to determine if a file is a symlink or not, we have to lstat(). + // If we're not interested in that information, we might as well avoid one + // extra syscall. Bundle detecton on Mac can be slow, expecially on network + // paths, so we separate out that as well. + + QAbstractFileEngine::FileFlags req = 0; + uint cachedFlags = 0; + + if (request & (QAbstractFileEngine::FlagsMask | QAbstractFileEngine::TypesMask)) { + if (!getCachedFlag(CachedFileFlags)) { + req |= QAbstractFileEngine::FlagsMask; + req |= QAbstractFileEngine::TypesMask; + req &= (~QAbstractFileEngine::LinkType); + req &= (~QAbstractFileEngine::BundleType); + + cachedFlags |= CachedFileFlags; + } + + if (request & QAbstractFileEngine::LinkType) { + if (!getCachedFlag(CachedLinkTypeFlag)) { + req |= QAbstractFileEngine::LinkType; + cachedFlags |= CachedLinkTypeFlag; + } + } + + if (request & QAbstractFileEngine::BundleType) { + if (!getCachedFlag(CachedBundleTypeFlag)) { + req |= QAbstractFileEngine::BundleType; + cachedFlags |= CachedBundleTypeFlag; + } + } + } + + if (request & QAbstractFileEngine::PermsMask) { + if (!getCachedFlag(CachedPerms)) { + req |= QAbstractFileEngine::PermsMask; + cachedFlags |= CachedPerms; + } + } + + if (req) { + if (cache_enabled) + req &= (~QAbstractFileEngine::Refresh); + else + req |= QAbstractFileEngine::Refresh; + + QAbstractFileEngine::FileFlags flags = fileEngine->fileFlags(req); + fileFlags |= uint(flags); + setCachedFlag(cachedFlags); + } + + return fileFlags & request; +} + +QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request) const +{ + Q_ASSERT(fileEngine); // should never be called when using the native FS + if (!cache_enabled) + clearFlags(); + uint cf; + if (request == QAbstractFileEngine::CreationTime) + cf = CachedCTime; + else if (request == QAbstractFileEngine::ModificationTime) + cf = CachedMTime; + else + cf = CachedATime; + if (!getCachedFlag(cf)) { + fileTimes[request] = fileEngine->fileTime(request); + setCachedFlag(cf); + } + return fileTimes[request]; +} + +//************* QFileInfo + +/*! + \class QFileInfo + \reentrant + \brief The QFileInfo class provides system-independent file information. + + \ingroup io + \ingroup shared + + QFileInfo provides information about a file's name and position + (path) in the file system, its access rights and whether it is a + directory or symbolic link, etc. The file's size and last + modified/read times are also available. QFileInfo can also be + used to obtain information about a Qt \l{resource + system}{resource}. + + A QFileInfo can point to a file with either a relative or an + absolute file path. Absolute file paths begin with the directory + separator "/" (or with a drive specification on Windows). Relative + file names begin with a directory name or a file name and specify + a path relative to the current working directory. An example of an + absolute path is the string "/tmp/quartz". A relative path might + look like "src/fatlib". You can use the function isRelative() to + check whether a QFileInfo is using a relative or an absolute file + path. You can call the function makeAbsolute() to convert a + relative QFileInfo's path to an absolute path. + + The file that the QFileInfo works on is set in the constructor or + later with setFile(). Use exists() to see if the file exists and + size() to get its size. + + The file's type is obtained with isFile(), isDir() and + isSymLink(). The symLinkTarget() function provides the name of the file + the symlink points to. + + On Unix (including Mac OS X), the symlink has the same size() has + the file it points to, because Unix handles symlinks + transparently; similarly, opening a symlink using QFile + effectively opens the link's target. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 0 + + On Windows, symlinks (shortcuts) are \c .lnk files. The reported + size() is that of the symlink (not the link's target), and + opening a symlink using QFile opens the \c .lnk file. For + example: + + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 1 + + Elements of the file's name can be extracted with path() and + fileName(). The fileName()'s parts can be extracted with + baseName(), suffix() or completeSuffix(). QFileInfo objects to + directories created by Qt classes will not have a trailing file + separator. If you wish to use trailing separators in your own file + info objects, just append one to the file name given to the constructors + or setFile(). + + The file's dates are returned by created(), lastModified() and + lastRead(). Information about the file's access permissions is + obtained with isReadable(), isWritable() and isExecutable(). The + file's ownership is available from owner(), ownerId(), group() and + groupId(). You can examine a file's permissions and ownership in a + single statement using the permission() function. + + \section1 Performance Issues + + Some of QFileInfo's functions query the file system, but for + performance reasons, some functions only operate on the + file name itself. For example: To return the absolute path of + a relative file name, absolutePath() has to query the file system. + The path() function, however, can work on the file name directly, + and so it is faster. + + \note To speed up performance, QFileInfo caches information about + the file. + + To speed up performance, QFileInfo caches information about the + file. Because files can be changed by other users or programs, or + even by other parts of the same program, there is a function that + refreshes the file information: refresh(). If you want to switch + off a QFileInfo's caching and force it to access the file system + every time you request information from it call setCaching(false). + + \sa QDir, QFile +*/ + +/*! + \internal +*/ +QFileInfo::QFileInfo(QFileInfoPrivate *p) : d_ptr(p) +{ +} + +/*! + Constructs an empty QFileInfo object. + + Note that an empty QFileInfo object contain no file reference. + + \sa setFile() +*/ +QFileInfo::QFileInfo() : d_ptr(new QFileInfoPrivate()) +{ +} + +/*! + Constructs a new QFileInfo that gives information about the given + file. The \a file can also include an absolute or relative path. + + \sa setFile(), isRelative(), QDir::setCurrent(), QDir::isRelativePath() +*/ +QFileInfo::QFileInfo(const QString &file) : d_ptr(new QFileInfoPrivate(file)) +{ +} + +/*! + Constructs a new QFileInfo that gives information about file \a + file. + + If the \a file has a relative path, the QFileInfo will also have a + relative path. + + \sa isRelative() +*/ +QFileInfo::QFileInfo(const QFile &file) : d_ptr(new QFileInfoPrivate(file.fileName())) +{ +} + +/*! + Constructs a new QFileInfo that gives information about the given + \a file in the directory \a dir. + + If \a dir has a relative path, the QFileInfo will also have a + relative path. + + If \a file is an absolute path, then the directory specified + by \a dir will be disregarded. + + \sa isRelative() +*/ +QFileInfo::QFileInfo(const QDir &dir, const QString &file) + : d_ptr(new QFileInfoPrivate(dir.filePath(file))) +{ +} + +/*! + Constructs a new QFileInfo that is a copy of the given \a fileinfo. +*/ +QFileInfo::QFileInfo(const QFileInfo &fileinfo) + : d_ptr(fileinfo.d_ptr) +{ + +} + +/*! + Destroys the QFileInfo and frees its resources. +*/ + +QFileInfo::~QFileInfo() +{ +} + +/*! + \fn bool QFileInfo::operator!=(const QFileInfo &fileinfo) + + Returns true if this QFileInfo object refers to a different file + than the one specified by \a fileinfo; otherwise returns false. + + \sa operator==() +*/ + +/*! + \overload + \fn bool QFileInfo::operator!=(const QFileInfo &fileinfo) const +*/ + +/*! + \overload +*/ +bool QFileInfo::operator==(const QFileInfo &fileinfo) const +{ + Q_D(const QFileInfo); + // ### Qt 5: understand long and short file names on Windows + // ### (GetFullPathName()). + if (fileinfo.d_ptr == d_ptr) + return true; + if (d->isDefaultConstructed || fileinfo.d_ptr->isDefaultConstructed) + return false; + Qt::CaseSensitivity sensitive; + if (d->fileEngine == 0 || fileinfo.d_ptr->fileEngine == 0) { + if (d->fileEngine != fileinfo.d_ptr->fileEngine) // one is native, the other is a custom file-engine + return false; + + sensitive = QFileSystemEngine::isCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive; + } else { + if (d->fileEngine->caseSensitive() != fileinfo.d_ptr->fileEngine->caseSensitive()) + return false; + sensitive = d->fileEngine->caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive; + } + + if (fileinfo.size() != size()) //if the size isn't the same... + return false; + + return canonicalFilePath().compare(fileinfo.canonicalFilePath(), sensitive) == 0; +} + +/*! + Returns true if this QFileInfo object refers to a file in the same + location as \a fileinfo; otherwise returns false. + + Note that the result of comparing two empty QFileInfo objects, + containing no file references, is undefined. + + \warning This will not compare two different symbolic links + pointing to the same file. + + \warning Long and short file names that refer to the same file on Windows + are treated as if they referred to different files. + + \sa operator!=() +*/ +bool QFileInfo::operator==(const QFileInfo &fileinfo) +{ + return const_cast<const QFileInfo *>(this)->operator==(fileinfo); +} + +/*! + Makes a copy of the given \a fileinfo and assigns it to this QFileInfo. +*/ +QFileInfo &QFileInfo::operator=(const QFileInfo &fileinfo) +{ + d_ptr = fileinfo.d_ptr; + return *this; +} + +/*! + Sets the file that the QFileInfo provides information about to \a + file. + + The \a file can also include an absolute or relative file path. + Absolute paths begin with the directory separator (e.g. "/" under + Unix) or a drive specification (under Windows). Relative file + names begin with a directory name or a file name and specify a + path relative to the current directory. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 2 + + \sa isRelative(), QDir::setCurrent(), QDir::isRelativePath() +*/ +void QFileInfo::setFile(const QString &file) +{ + bool caching = d_ptr.constData()->cache_enabled; + *this = QFileInfo(file); + d_ptr->cache_enabled = caching; +} + +/*! + \overload + + Sets the file that the QFileInfo provides information about to \a + file. + + If \a file includes a relative path, the QFileInfo will also have + a relative path. + + \sa isRelative() +*/ +void QFileInfo::setFile(const QFile &file) +{ + setFile(file.fileName()); +} + +/*! + \overload + + Sets the file that the QFileInfo provides information about to \a + file in directory \a dir. + + If \a file includes a relative path, the QFileInfo will also + have a relative path. + + \sa isRelative() +*/ +void QFileInfo::setFile(const QDir &dir, const QString &file) +{ + setFile(dir.filePath(file)); +} + +/*! + Returns an absolute path including the file name. + + The absolute path name consists of the full path and the file + name. On Unix this will always begin with the root, '/', + directory. On Windows this will always begin 'D:/' where D is a + drive letter, except for network shares that are not mapped to a + drive letter, in which case the path will begin '//sharename/'. + QFileInfo will uppercase drive letters. Note that QDir does not do + this. The code snippet below shows this. + + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp newstuff + + This function returns the same as filePath(), unless isRelative() + is true. In contrast to canonicalFilePath(), symbolic links or + redundant "." or ".." elements are not necessarily removed. + + If the QFileInfo is empty it returns QDir::currentPath(). + + \sa filePath(), canonicalFilePath(), isRelative() +*/ +QString QFileInfo::absoluteFilePath() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->getFileName(QAbstractFileEngine::AbsoluteName); +} + +/*! + Returns the canonical path including the file name, i.e. an absolute + path without symbolic links or redundant "." or ".." elements. + + If the file does not exist, canonicalFilePath() returns an empty + string. + + \sa filePath(), absoluteFilePath(), dir() +*/ +QString QFileInfo::canonicalFilePath() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->getFileName(QAbstractFileEngine::CanonicalName); +} + + +/*! + Returns a file's path absolute path. This doesn't include the + file name. + + On Unix the absolute path will always begin with the root, '/', + directory. On Windows this will always begin 'D:/' where D is a + drive letter, except for network shares that are not mapped to a + drive letter, in which case the path will begin '//sharename/'. + + In contrast to canonicalPath() symbolic links or redundant "." or + ".." elements are not necessarily removed. + + \warning If the QFileInfo object was created with an empty QString, + the behavior of this function is undefined. + + \sa absoluteFilePath(), path(), canonicalPath(), fileName(), isRelative() +*/ +QString QFileInfo::absolutePath() const +{ + Q_D(const QFileInfo); + + if (d->isDefaultConstructed) { + return QLatin1String(""); + } else if (d->fileEntry.isEmpty()) { + qWarning("QFileInfo::absolutePath: Constructed with empty filename"); + return QLatin1String(""); + } + return d->getFileName(QAbstractFileEngine::AbsolutePathName); +} + +/*! + Returns the file's path canonical path (excluding the file name), + i.e. an absolute path without symbolic links or redundant "." or ".." elements. + + If the file does not exist, canonicalPath() returns an empty string. + + \sa path(), absolutePath() +*/ +QString QFileInfo::canonicalPath() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->getFileName(QAbstractFileEngine::CanonicalPathName); +} + +/*! + Returns the file's path. This doesn't include the file name. + + Note that, if this QFileInfo object is given a path ending in a + slash, the name of the file is considered empty and this function + will return the entire path. + + \sa filePath(), absolutePath(), canonicalPath(), dir(), fileName(), isRelative() +*/ +QString QFileInfo::path() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->fileEntry.path(); +} + +/*! + \fn bool QFileInfo::isAbsolute() const + + Returns true if the file path name is absolute, otherwise returns + false if the path is relative. + + \sa isRelative() +*/ + +/*! + Returns true if the file path name is relative, otherwise returns + false if the path is absolute (e.g. under Unix a path is absolute + if it begins with a "/"). + + \sa isAbsolute() +*/ +bool QFileInfo::isRelative() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return true; + if (d->fileEngine == 0) + return d->fileEntry.isRelative(); + return d->fileEngine->isRelativePath(); +} + +/*! + Converts the file's path to an absolute path if it is not already in that form. + Returns true to indicate that the path was converted; otherwise returns false + to indicate that the path was already absolute. + + \sa filePath(), isRelative() +*/ +bool QFileInfo::makeAbsolute() +{ + if (d_ptr.constData()->isDefaultConstructed + || !d_ptr.constData()->fileEntry.isRelative()) + return false; + + setFile(absoluteFilePath()); + return true; +} + +/*! + Returns true if the file exists; otherwise returns false. + + \note If the file is a symlink that points to a non existing + file, false is returned. +*/ +bool QFileInfo::exists() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return false; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::ExistsAttribute)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::ExistsAttribute); + return d->metaData.exists(); + } + return d->getFileFlags(QAbstractFileEngine::ExistsFlag); +} + +/*! + Refreshes the information about the file, i.e. reads in information + from the file system the next time a cached property is fetched. + + \note On Windows CE, there might be a delay for the file system driver + to detect changes on the file. +*/ +void QFileInfo::refresh() +{ + Q_D(QFileInfo); + d->clear(); +} + +/*! + Returns the file name, including the path (which may be absolute + or relative). + + \sa absoluteFilePath(), canonicalFilePath(), isRelative() +*/ +QString QFileInfo::filePath() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->fileEntry.filePath(); +} + +/*! + Returns the name of the file, excluding the path. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 3 + + Note that, if this QFileInfo object is given a path ending in a + slash, the name of the file is considered empty. + + \sa isRelative(), filePath(), baseName(), extension() +*/ +QString QFileInfo::fileName() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->fileEntry.fileName(); +} + +/*! + \since 4.3 + Returns the name of the bundle. + + On Mac OS X this returns the proper localized name for a bundle if the + path isBundle(). On all other platforms an empty QString is returned. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 4 + + \sa isBundle(), filePath(), baseName(), extension() +*/ +QString QFileInfo::bundleName() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->getFileName(QAbstractFileEngine::BundleName); +} + +/*! + Returns the base name of the file without the path. + + The base name consists of all characters in the file up to (but + not including) the \e first '.' character. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 5 + + + The base name of a file is computed equally on all platforms, independent + of file naming conventions (e.g., ".bashrc" on Unix has an empty base + name, and the suffix is "bashrc"). + + \sa fileName(), suffix(), completeSuffix(), completeBaseName() +*/ +QString QFileInfo::baseName() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->fileEntry.baseName(); +} + +/*! + Returns the complete base name of the file without the path. + + The complete base name consists of all characters in the file up + to (but not including) the \e last '.' character. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 6 + + \sa fileName(), suffix(), completeSuffix(), baseName() +*/ +QString QFileInfo::completeBaseName() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->fileEntry.completeBaseName(); +} + +/*! + Returns the complete suffix of the file. + + The complete suffix consists of all characters in the file after + (but not including) the first '.'. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 7 + + \sa fileName(), suffix(), baseName(), completeBaseName() +*/ +QString QFileInfo::completeSuffix() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->fileEntry.completeSuffix(); +} + +/*! + Returns the suffix of the file. + + The suffix consists of all characters in the file after (but not + including) the last '.'. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 8 + + The suffix of a file is computed equally on all platforms, independent of + file naming conventions (e.g., ".bashrc" on Unix has an empty base name, + and the suffix is "bashrc"). + + \sa fileName(), completeSuffix(), baseName(), completeBaseName() +*/ +QString QFileInfo::suffix() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->fileEntry.suffix(); +} + + +/*! + Returns the path of the object's parent directory as a QDir object. + + \bold{Note:} The QDir returned always corresponds to the object's + parent directory, even if the QFileInfo represents a directory. + + For each of the following, dir() returns a QDir for + \c{"~/examples/191697"}. + + \snippet doc/src/snippets/fileinfo/main.cpp 0 + + For each of the following, dir() returns a QDir for + \c{"."}. + + \snippet doc/src/snippets/fileinfo/main.cpp 1 + + \sa absolutePath(), filePath(), fileName(), isRelative(), absoluteDir() +*/ +QDir QFileInfo::dir() const +{ + Q_D(const QFileInfo); + // ### Qt5: Maybe rename this to parentDirectory(), considering what it actually do? + return QDir(d->fileEntry.path()); +} + +/*! + Returns the file's absolute path as a QDir object. + + \sa dir(), filePath(), fileName(), isRelative() +*/ +QDir QFileInfo::absoluteDir() const +{ + return QDir(absolutePath()); +} + +#ifdef QT3_SUPPORT +/*! + Use absoluteDir() or the dir() overload that takes no parameters + instead. +*/ +QDir QFileInfo::dir(bool absPath) const +{ + if (absPath) + return absoluteDir(); + return dir(); +} +#endif //QT3_SUPPORT + +/*! + Returns true if the user can read the file; otherwise returns false. + + \sa isWritable(), isExecutable(), permission() +*/ +bool QFileInfo::isReadable() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return false; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::UserReadPermission)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::UserReadPermission); + return (d->metaData.permissions() & QFile::ReadUser) != 0; + } + return d->getFileFlags(QAbstractFileEngine::ReadUserPerm); +} + +/*! + Returns true if the user can write to the file; otherwise returns false. + + \sa isReadable(), isExecutable(), permission() +*/ +bool QFileInfo::isWritable() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return false; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::UserWritePermission)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::UserWritePermission); + return (d->metaData.permissions() & QFile::WriteUser) != 0; + } + return d->getFileFlags(QAbstractFileEngine::WriteUserPerm); +} + +/*! + Returns true if the file is executable; otherwise returns false. + + \sa isReadable(), isWritable(), permission() +*/ +bool QFileInfo::isExecutable() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return false; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::UserExecutePermission)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::UserExecutePermission); + return (d->metaData.permissions() & QFile::ExeUser) != 0; + } + return d->getFileFlags(QAbstractFileEngine::ExeUserPerm); +} + +/*! + Returns true if this is a `hidden' file; otherwise returns false. + + \bold{Note:} This function returns true for the special entries + "." and ".." on Unix, even though QDir::entryList threats them as shown. +*/ +bool QFileInfo::isHidden() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return false; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::HiddenAttribute)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::HiddenAttribute); + return d->metaData.isHidden(); + } + return d->getFileFlags(QAbstractFileEngine::HiddenFlag); +} + +/*! + Returns true if this object points to a file or to a symbolic + link to a file. Returns false if the + object points to something which isn't a file, such as a directory. + + \sa isDir(), isSymLink(), isBundle() +*/ +bool QFileInfo::isFile() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return false; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::FileType)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::FileType); + return d->metaData.isFile(); + } + return d->getFileFlags(QAbstractFileEngine::FileType); +} + +/*! + Returns true if this object points to a directory or to a symbolic + link to a directory; otherwise returns false. + + \sa isFile(), isSymLink(), isBundle() +*/ +bool QFileInfo::isDir() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return false; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::DirectoryType)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::DirectoryType); + return d->metaData.isDirectory(); + } + return d->getFileFlags(QAbstractFileEngine::DirectoryType); +} + + +/*! + \since 4.3 + Returns true if this object points to a bundle or to a symbolic + link to a bundle on Mac OS X; otherwise returns false. + + \sa isDir(), isSymLink(), isFile() +*/ +bool QFileInfo::isBundle() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return false; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::BundleType)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::BundleType); + return d->metaData.isBundle(); + } + return d->getFileFlags(QAbstractFileEngine::BundleType); +} + +/*! + Returns true if this object points to a symbolic link (or to a + shortcut on Windows); otherwise returns false. + + On Unix (including Mac OS X), opening a symlink effectively opens + the \l{symLinkTarget()}{link's target}. On Windows, it opens the \c + .lnk file itself. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 9 + + \note If the symlink points to a non existing file, exists() returns + false. + + \sa isFile(), isDir(), symLinkTarget() +*/ +bool QFileInfo::isSymLink() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return false; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::LegacyLinkType)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::LegacyLinkType); + return d->metaData.isLegacyLink(); + } + return d->getFileFlags(QAbstractFileEngine::LinkType); +} + +/*! + Returns true if the object points to a directory or to a symbolic + link to a directory, and that directory is the root directory; otherwise + returns false. +*/ +bool QFileInfo::isRoot() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return true; + if (d->fileEngine == 0) { + if (d->fileEntry.isRoot()) { +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + //the path is a drive root, but the drive may not exist + //for backward compatibility, return true only if the drive exists + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::ExistsAttribute)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::ExistsAttribute); + return d->metaData.exists(); +#else + return true; +#endif + } + return false; + } + return d->getFileFlags(QAbstractFileEngine::RootFlag); +} + +/*! + \fn QString QFileInfo::symLinkTarget() const + \since 4.2 + + Returns the absolute path to the file or directory a symlink (or shortcut + on Windows) points to, or a an empty string if the object isn't a symbolic + link. + + This name may not represent an existing file; it is only a string. + QFileInfo::exists() returns true if the symlink points to an + existing file. + + \sa exists(), isSymLink(), isDir(), isFile() +*/ + +/*! + \obsolete + + Use symLinkTarget() instead. +*/ +QString QFileInfo::readLink() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->getFileName(QAbstractFileEngine::LinkName); +} + +/*! + Returns the owner of the file. On systems where files + do not have owners, or if an error occurs, an empty string is + returned. + + This function can be time consuming under Unix (in the order of + milliseconds). + + \sa ownerId(), group(), groupId() +*/ +QString QFileInfo::owner() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->getFileOwner(QAbstractFileEngine::OwnerUser); +} + +/*! + Returns the id of the owner of the file. + + On Windows and on systems where files do not have owners this + function returns ((uint) -2). + + \sa owner(), group(), groupId() +*/ +uint QFileInfo::ownerId() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return 0; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::UserId)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::UserId); + return d->metaData.userId(); + } + return d->fileEngine->ownerId(QAbstractFileEngine::OwnerUser); +} + +/*! + Returns the group of the file. On Windows, on systems where files + do not have groups, or if an error occurs, an empty string is + returned. + + This function can be time consuming under Unix (in the order of + milliseconds). + + \sa groupId(), owner(), ownerId() +*/ +QString QFileInfo::group() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->getFileOwner(QAbstractFileEngine::OwnerGroup); +} + +/*! + Returns the id of the group the file belongs to. + + On Windows and on systems where files do not have groups this + function always returns (uint) -2. + + \sa group(), owner(), ownerId() +*/ +uint QFileInfo::groupId() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return 0; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::GroupId)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::GroupId); + return d->metaData.groupId(); + } + return d->fileEngine->ownerId(QAbstractFileEngine::OwnerGroup); +} + +/*! + Tests for file permissions. The \a permissions argument can be + several flags of type QFile::Permissions OR-ed together to check + for permission combinations. + + On systems where files do not have permissions this function + always returns true. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qfileinfo.cpp 10 + + \sa isReadable(), isWritable(), isExecutable() +*/ +bool QFileInfo::permission(QFile::Permissions permissions) const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return false; + if (d->fileEngine == 0) { + // the QFileSystemMetaData::MetaDataFlag and QFile::Permissions overlap, so just static cast. + QFileSystemMetaData::MetaDataFlag permissionFlags = static_cast<QFileSystemMetaData::MetaDataFlag>((int)permissions); + if (!d->cache_enabled || !d->metaData.hasFlags(permissionFlags)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, permissionFlags); + return (d->metaData.permissions() & permissions) == permissions; + } + return d->getFileFlags(QAbstractFileEngine::FileFlags((int)permissions)) == (uint)permissions; +} + +/*! + Returns the complete OR-ed together combination of + QFile::Permissions for the file. +*/ +QFile::Permissions QFileInfo::permissions() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return 0; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::Permissions)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::Permissions); + return d->metaData.permissions(); + } + return QFile::Permissions(d->getFileFlags(QAbstractFileEngine::PermsMask) & QAbstractFileEngine::PermsMask); +} + + +/*! + Returns the file size in bytes. If the file does not exist or cannot be + fetched, 0 is returned. + + \sa exists() +*/ +qint64 QFileInfo::size() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return 0; + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::SizeAttribute)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::SizeAttribute); + return d->metaData.size(); + } + if (!d->getCachedFlag(QFileInfoPrivate::CachedSize)) { + d->setCachedFlag(QFileInfoPrivate::CachedSize); + d->fileSize = d->fileEngine->size(); + } + return d->fileSize; +} + +/*! + Returns the date and time when the file was created. + + On most Unix systems, this function returns the time of the last + status change. A status change occurs when the file is created, + but it also occurs whenever the user writes or sets inode + information (for example, changing the file permissions). + + If neither creation time nor "last status change" time are not + available, returns the same as lastModified(). + + \sa lastModified() lastRead() +*/ +QDateTime QFileInfo::created() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QDateTime(); + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::CreationTime)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::CreationTime); + return d->metaData.creationTime(); + } + return d->getFileTime(QAbstractFileEngine::CreationTime); +} + +/*! + Returns the date and time when the file was last modified. + + \sa created() lastRead() +*/ +QDateTime QFileInfo::lastModified() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QDateTime(); + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::ModificationTime)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::ModificationTime); + return d->metaData.modificationTime(); + } + return d->getFileTime(QAbstractFileEngine::ModificationTime); +} + +/*! + Returns the date and time when the file was last read (accessed). + + On platforms where this information is not available, returns the + same as lastModified(). + + \sa created() lastModified() +*/ +QDateTime QFileInfo::lastRead() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QDateTime(); + if (d->fileEngine == 0) { + if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::AccessTime)) + QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::AccessTime); + return d->metaData.accessTime(); + } + return d->getFileTime(QAbstractFileEngine::AccessTime); +} + +/*! \internal + Detaches all internal data. +*/ +void QFileInfo::detach() +{ + d_ptr.detach(); +} + +/*! + Returns true if caching is enabled; otherwise returns false. + + \sa setCaching(), refresh() +*/ +bool QFileInfo::caching() const +{ + Q_D(const QFileInfo); + return d->cache_enabled; +} + +/*! + If \a enable is true, enables caching of file information. If \a + enable is false caching is disabled. + + When caching is enabled, QFileInfo reads the file information from + the file system the first time it's needed, but generally not + later. + + Caching is enabled by default. + + \sa refresh(), caching() +*/ +void QFileInfo::setCaching(bool enable) +{ + Q_D(QFileInfo); + d->cache_enabled = enable; +} + +/*! + \fn QString QFileInfo::baseName(bool complete) + + Use completeBaseName() or the baseName() overload that takes no + parameters instead. +*/ + +/*! + \fn QString QFileInfo::extension(bool complete = true) const + + Use completeSuffix() or suffix() instead. +*/ + +/*! + \fn QString QFileInfo::absFilePath() const + + Use absoluteFilePath() instead. +*/ + +/*! + \fn QString QFileInfo::dirPath(bool absPath) const + + Use absolutePath() if the absolute path is wanted (\a absPath + is true) or path() if it's not necessary (\a absPath is false). +*/ + +/*! + \fn bool QFileInfo::convertToAbs() + + Use makeAbsolute() instead. +*/ + +/*! + \enum QFileInfo::Permission + + \compat + + \value ReadOwner + \value WriteOwner + \value ExeOwner + \value ReadUser + \value WriteUser + \value ExeUser + \value ReadGroup + \value WriteGroup + \value ExeGroup + \value ReadOther + \value WriteOther + \value ExeOther +*/ + +/*! + \fn bool QFileInfo::permission(PermissionSpec permissions) const + \compat + + Use permission() instead. +*/ + +/*! + \typedef QFileInfoList + \relates QFileInfo + + Synonym for QList<QFileInfo>. +*/ + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfileinfo.h b/src/corelib/io/qfileinfo.h new file mode 100644 index 0000000000..5cfefb3a95 --- /dev/null +++ b/src/corelib/io/qfileinfo.h @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEINFO_H +#define QFILEINFO_H + +#include <QtCore/qfile.h> +#include <QtCore/qlist.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QDir; +class QDirIteratorPrivate; +class QDateTime; +class QFileInfoPrivate; + +class Q_CORE_EXPORT QFileInfo +{ + friend class QDirIteratorPrivate; +public: + explicit QFileInfo(QFileInfoPrivate *d); + + QFileInfo(); + QFileInfo(const QString &file); + QFileInfo(const QFile &file); + QFileInfo(const QDir &dir, const QString &file); + QFileInfo(const QFileInfo &fileinfo); + ~QFileInfo(); + + QFileInfo &operator=(const QFileInfo &fileinfo); +#ifdef Q_COMPILER_RVALUE_REFS + inline QFileInfo&operator=(QFileInfo &&other) + { qSwap(d_ptr, other.d_ptr); return *this; } +#endif + bool operator==(const QFileInfo &fileinfo); // 5.0 - remove me + bool operator==(const QFileInfo &fileinfo) const; + inline bool operator!=(const QFileInfo &fileinfo) { return !(operator==(fileinfo)); } // 5.0 - remove me + inline bool operator!=(const QFileInfo &fileinfo) const { return !(operator==(fileinfo)); } + + void setFile(const QString &file); + void setFile(const QFile &file); + void setFile(const QDir &dir, const QString &file); + bool exists() const; + void refresh(); + + QString filePath() const; + QString absoluteFilePath() const; + QString canonicalFilePath() const; + QString fileName() const; + QString baseName() const; + QString completeBaseName() const; + QString suffix() const; + QString bundleName() const; + QString completeSuffix() const; + + QString path() const; + QString absolutePath() const; + QString canonicalPath() const; + QDir dir() const; + QDir absoluteDir() const; + + bool isReadable() const; + bool isWritable() const; + bool isExecutable() const; + bool isHidden() const; + + bool isRelative() const; + inline bool isAbsolute() const { return !isRelative(); } + bool makeAbsolute(); + + bool isFile() const; + bool isDir() const; + bool isSymLink() const; + bool isRoot() const; + bool isBundle() const; + + QString readLink() const; + inline QString symLinkTarget() const { return readLink(); } + + QString owner() const; + uint ownerId() const; + QString group() const; + uint groupId() const; + + bool permission(QFile::Permissions permissions) const; + QFile::Permissions permissions() const; + + qint64 size() const; + + QDateTime created() const; + QDateTime lastModified() const; + QDateTime lastRead() const; + + void detach(); + + bool caching() const; + void setCaching(bool on); + +#ifdef QT3_SUPPORT + enum Permission { + ReadOwner = QFile::ReadOwner, WriteOwner = QFile::WriteOwner, ExeOwner = QFile::ExeOwner, + ReadUser = QFile::ReadUser, WriteUser = QFile::WriteUser, ExeUser = QFile::ExeUser, + ReadGroup = QFile::ReadGroup, WriteGroup = QFile::WriteGroup, ExeGroup = QFile::ExeGroup, + ReadOther = QFile::ReadOther, WriteOther = QFile::WriteOther, ExeOther = QFile::ExeOther + }; + Q_DECLARE_FLAGS(PermissionSpec, Permission) + + inline QT3_SUPPORT QString baseName(bool complete) { + if(complete) + return completeBaseName(); + return baseName(); + } + inline QT3_SUPPORT QString extension(bool complete = true) const { + if(complete) + return completeSuffix(); + return suffix(); + } + inline QT3_SUPPORT QString absFilePath() const { return absoluteFilePath(); } + + inline QT3_SUPPORT QString dirPath(bool absPath = false) const { + if(absPath) + return absolutePath(); + return path(); + } + QT3_SUPPORT QDir dir(bool absPath) const; + inline QT3_SUPPORT bool convertToAbs() { return makeAbsolute(); } +#if !defined(Q_NO_TYPESAFE_FLAGS) + inline QT3_SUPPORT bool permission(PermissionSpec permissions) const + { return permission(QFile::Permissions(static_cast<int>(permissions))); } +#endif +#endif + +protected: + QSharedDataPointer<QFileInfoPrivate> d_ptr; +private: + inline QFileInfoPrivate* d_func() + { + detach(); + return const_cast<QFileInfoPrivate *>(d_ptr.constData()); + } + + inline const QFileInfoPrivate* d_func() const + { + return d_ptr.constData(); + } +}; + +Q_DECLARE_TYPEINFO(QFileInfo, Q_MOVABLE_TYPE); + +#ifdef QT3_SUPPORT +Q_DECLARE_OPERATORS_FOR_FLAGS(QFileInfo::PermissionSpec) +#endif + +typedef QList<QFileInfo> QFileInfoList; +#ifdef QT3_SUPPORT +typedef QList<QFileInfo>::Iterator QFileInfoListIterator; +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFILEINFO_H diff --git a/src/corelib/io/qfileinfo_p.h b/src/corelib/io/qfileinfo_p.h new file mode 100644 index 0000000000..db904c76bb --- /dev/null +++ b/src/corelib/io/qfileinfo_p.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEINFO_P_H +#define QFILEINFO_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qfileinfo.h" +#include "qabstractfileengine.h" +#include "qdatetime.h" +#include "qatomic.h" +#include "qshareddata.h" +#include "qfilesystemengine_p.h" + +#include <QtCore/private/qfilesystementry_p.h> +#include <QtCore/private/qfilesystemmetadata_p.h> + +QT_BEGIN_NAMESPACE + +class QFileInfoPrivate : public QSharedData +{ +public: + enum { CachedFileFlags=0x01, CachedLinkTypeFlag=0x02, CachedBundleTypeFlag=0x04, + CachedMTime=0x10, CachedCTime=0x20, CachedATime=0x40, + CachedSize =0x08, CachedPerms=0x80 }; + + inline QFileInfoPrivate() + : QSharedData(), fileEngine(0), + cachedFlags(0), + isDefaultConstructed(true), + cache_enabled(true), fileFlags(0), fileSize(0) + {} + inline QFileInfoPrivate(const QFileInfoPrivate ©) + : QSharedData(copy), + fileEntry(copy.fileEntry), + metaData(copy.metaData), + fileEngine(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(fileEntry, metaData)), + cachedFlags(0), +#ifndef QT_NO_FSFILEENGINE + isDefaultConstructed(false), +#else + isDefaultConstructed(!fileEngine), +#endif + cache_enabled(copy.cache_enabled), fileFlags(0), fileSize(0) + {} + inline QFileInfoPrivate(const QString &file) + : fileEntry(QDir::fromNativeSeparators(file)), + fileEngine(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(fileEntry, metaData)), + cachedFlags(0), +#ifndef QT_NO_FSFILEENGINE + isDefaultConstructed(false), +#else + isDefaultConstructed(!fileEngine), +#endif + cache_enabled(true), fileFlags(0), fileSize(0) + { + } + + inline QFileInfoPrivate(const QFileSystemEntry &file, const QFileSystemMetaData &data) + : QSharedData(), + fileEntry(file), + metaData(data), + cachedFlags(0), + isDefaultConstructed(false), + cache_enabled(true), fileFlags(0), fileSize(0) + { + } + + inline void clearFlags() const { + fileFlags = 0; + cachedFlags = 0; + if (fileEngine) + (void)fileEngine->fileFlags(QAbstractFileEngine::Refresh); + } + inline void clear() { + metaData.clear(); + clearFlags(); + for (int i = QAbstractFileEngine::NFileNames - 1 ; i >= 0 ; --i) + fileNames[i].clear(); + fileOwners[1].clear(); + fileOwners[0].clear(); + } + + uint getFileFlags(QAbstractFileEngine::FileFlags) const; + QDateTime &getFileTime(QAbstractFileEngine::FileTime) const; + QString getFileName(QAbstractFileEngine::FileName) const; + QString getFileOwner(QAbstractFileEngine::FileOwner own) const; + + QFileSystemEntry fileEntry; + mutable QFileSystemMetaData metaData; + + QScopedPointer<QAbstractFileEngine> const fileEngine; + + mutable QString fileNames[QAbstractFileEngine::NFileNames]; + mutable QString fileOwners[2]; + + mutable uint cachedFlags : 30; + bool const isDefaultConstructed : 1; // QFileInfo is a default constructed instance + bool cache_enabled : 1; + mutable uint fileFlags; + mutable qint64 fileSize; + mutable QDateTime fileTimes[3]; + inline bool getCachedFlag(uint c) const + { return cache_enabled ? (cachedFlags & c) : 0; } + inline void setCachedFlag(uint c) const + { if (cache_enabled) cachedFlags |= c; } + +}; + +QT_END_NAMESPACE + +#endif // QFILEINFO_P_H diff --git a/src/corelib/io/qfilesystemengine.cpp b/src/corelib/io/qfilesystemengine.cpp new file mode 100644 index 0000000000..00c33bd797 --- /dev/null +++ b/src/corelib/io/qfilesystemengine.cpp @@ -0,0 +1,391 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemengine_p.h" +#include <QtCore/qdir.h> +#include <QtCore/qset.h> +#include <QtCore/qstringbuilder.h> +#include <QtCore/private/qabstractfileengine_p.h> +#ifdef QT_BUILD_CORE_LIB +#include <QtCore/private/qresource_p.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \internal + + Returns the canonicalized form of \a path (i.e., with all symlinks + resolved, and all redundant path elements removed. +*/ +QString QFileSystemEngine::slowCanonicalized(const QString &path) +{ + if (path.isEmpty()) + return path; + + QFileInfo fi; + const QChar slash(QLatin1Char('/')); + QString tmpPath = path; + int separatorPos = 0; + QSet<QString> nonSymlinks; + QSet<QString> known; + + known.insert(path); + do { +#ifdef Q_OS_WIN + if (separatorPos == 0) { + if (tmpPath.size() >= 2 && tmpPath.at(0) == slash && tmpPath.at(1) == slash) { + // UNC, skip past the first two elements + separatorPos = tmpPath.indexOf(slash, 2); + } else if (tmpPath.size() >= 3 && tmpPath.at(1) == QLatin1Char(':') && tmpPath.at(2) == slash) { + // volume root, skip since it can not be a symlink + separatorPos = 2; + } + } + if (separatorPos != -1) +#endif + separatorPos = tmpPath.indexOf(slash, separatorPos + 1); + QString prefix = separatorPos == -1 ? tmpPath : tmpPath.left(separatorPos); + if ( +#ifdef Q_OS_SYMBIAN + // Symbian doesn't support directory symlinks, so do not check for link unless we + // are handling the last path element. This not only slightly improves performance, + // but also saves us from lot of unnecessary platform security check failures + // when dealing with files under *:/private directories. + separatorPos == -1 && +#endif + !nonSymlinks.contains(prefix)) { + fi.setFile(prefix); + if (fi.isSymLink()) { + QString target = fi.symLinkTarget(); + if(QFileInfo(target).isRelative()) + target = fi.absolutePath() + slash + target; + if (separatorPos != -1) { + if (fi.isDir() && !target.endsWith(slash)) + target.append(slash); + target.append(tmpPath.mid(separatorPos)); + } + tmpPath = QDir::cleanPath(target); + separatorPos = 0; + + if (known.contains(tmpPath)) + return QString(); + known.insert(tmpPath); + } else { + nonSymlinks.insert(prefix); + } + } + } while (separatorPos != -1); + + return QDir::cleanPath(tmpPath); +} + +static inline bool _q_checkEntry(QFileSystemEntry &entry, QFileSystemMetaData &data, bool resolvingEntry) +{ + if (resolvingEntry) { + if (!QFileSystemEngine::fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute) + || !data.exists()) { + data.clear(); + return false; + } + } + + return true; +} + +static inline bool _q_checkEntry(QAbstractFileEngine *&engine, bool resolvingEntry) +{ + if (resolvingEntry) { + if (!(engine->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::ExistsFlag)) { + delete engine; + engine = 0; + return false; + } + } + + return true; +} + +static bool _q_resolveEntryAndCreateLegacyEngine_recursive(QFileSystemEntry &entry, QFileSystemMetaData &data, + QAbstractFileEngine *&engine, bool resolvingEntry = false) +{ + QString const &filePath = entry.filePath(); + if ((engine = qt_custom_file_engine_handler_create(filePath))) + return _q_checkEntry(engine, resolvingEntry); + +#if defined(QT_BUILD_CORE_LIB) + for (int prefixSeparator = 0; prefixSeparator < filePath.size(); ++prefixSeparator) { + QChar const ch = filePath[prefixSeparator]; + if (ch == QLatin1Char('/')) + break; + + if (ch == QLatin1Char(':')) { + if (prefixSeparator == 0) { + engine = new QResourceFileEngine(filePath); + return _q_checkEntry(engine, resolvingEntry); + } + + if (prefixSeparator == 1) + break; + + const QStringList &paths = QDir::searchPaths(filePath.left(prefixSeparator)); + for (int i = 0; i < paths.count(); i++) { + entry = QFileSystemEntry(QDir::cleanPath(paths.at(i) % QLatin1Char('/') % filePath.mid(prefixSeparator + 1))); + // Recurse! + if (_q_resolveEntryAndCreateLegacyEngine_recursive(entry, data, engine, true)) + return true; + } + + // entry may have been clobbered at this point. + return false; + } + + // There's no need to fully validate the prefix here. Consulting the + // unicode tables could be expensive and validation is already + // performed in QDir::setSearchPaths. + // + // if (!ch.isLetterOrNumber()) + // break; + } +#endif // defined(QT_BUILD_CORE_LIB) + + return _q_checkEntry(entry, data, resolvingEntry); +} + +/*! + \internal + + Resolves the \a entry (see QDir::searchPaths) and returns an engine for + it, but never a QFSFileEngine. + + \returns a file engine that can be used to access the entry. Returns 0 if + QFileSystemEngine API should be used to query and interact with the file + system object. +*/ +QAbstractFileEngine *QFileSystemEngine::resolveEntryAndCreateLegacyEngine( + QFileSystemEntry &entry, QFileSystemMetaData &data) { + QFileSystemEntry copy = entry; + QAbstractFileEngine *engine = 0; + + if (_q_resolveEntryAndCreateLegacyEngine_recursive(copy, data, engine)) + // Reset entry to resolved copy. + entry = copy; + else + data.clear(); + + return engine; +} + +//these unix functions are in this file, because they are shared by symbian port +//for open C file handles. +#ifdef Q_OS_UNIX +//static +bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data) +{ + data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags; + data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags; + + QT_STATBUF statBuffer; + if (QT_FSTAT(fd, &statBuffer) == 0) { + data.fillFromStatBuf(statBuffer); + return true; + } + + return false; +} + +void QFileSystemMetaData::fillFromStatBuf(const QT_STATBUF &statBuffer) +{ + // Permissions + if (statBuffer.st_mode & S_IRUSR) + entryFlags |= QFileSystemMetaData::OwnerReadPermission; + if (statBuffer.st_mode & S_IWUSR) + entryFlags |= QFileSystemMetaData::OwnerWritePermission; + if (statBuffer.st_mode & S_IXUSR) + entryFlags |= QFileSystemMetaData::OwnerExecutePermission; + + if (statBuffer.st_mode & S_IRGRP) + entryFlags |= QFileSystemMetaData::GroupReadPermission; + if (statBuffer.st_mode & S_IWGRP) + entryFlags |= QFileSystemMetaData::GroupWritePermission; + if (statBuffer.st_mode & S_IXGRP) + entryFlags |= QFileSystemMetaData::GroupExecutePermission; + + if (statBuffer.st_mode & S_IROTH) + entryFlags |= QFileSystemMetaData::OtherReadPermission; + if (statBuffer.st_mode & S_IWOTH) + entryFlags |= QFileSystemMetaData::OtherWritePermission; + if (statBuffer.st_mode & S_IXOTH) + entryFlags |= QFileSystemMetaData::OtherExecutePermission; + + // Type + if ((statBuffer.st_mode & S_IFMT) == S_IFREG) + entryFlags |= QFileSystemMetaData::FileType; + else if ((statBuffer.st_mode & S_IFMT) == S_IFDIR) + entryFlags |= QFileSystemMetaData::DirectoryType; + else + entryFlags |= QFileSystemMetaData::SequentialType; + + // Attributes + entryFlags |= QFileSystemMetaData::ExistsAttribute; + size_ = statBuffer.st_size; +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + if (statBuffer.st_flags & UF_HIDDEN) { + entryFlags |= QFileSystemMetaData::HiddenAttribute; + knownFlagsMask |= QFileSystemMetaData::HiddenAttribute; + } +#endif + + // Times +#ifdef Q_OS_SYMBIAN + modificationTime_ = qt_symbian_time_t_To_TTime(statBuffer.st_mtime); +#else + creationTime_ = statBuffer.st_ctime ? statBuffer.st_ctime : statBuffer.st_mtime; + modificationTime_ = statBuffer.st_mtime; + accessTime_ = statBuffer.st_atime; + userId_ = statBuffer.st_uid; + groupId_ = statBuffer.st_gid; +#endif +} + +void QFileSystemMetaData::fillFromDirEnt(const QT_DIRENT &entry) +{ +#if defined(_DIRENT_HAVE_D_TYPE) || defined(Q_OS_BSD4) || defined(Q_OS_SYMBIAN) + // BSD4 includes Mac OS X + + // ### This will clear all entry flags and knownFlagsMask + switch (entry.d_type) + { + case DT_DIR: + knownFlagsMask = QFileSystemMetaData::LinkType + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::SequentialType + | QFileSystemMetaData::ExistsAttribute; + + entryFlags = QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::ExistsAttribute; + + break; + + case DT_BLK: + case DT_CHR: + case DT_FIFO: + case DT_SOCK: + // ### System attribute + knownFlagsMask = QFileSystemMetaData::LinkType + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::BundleType + | QFileSystemMetaData::AliasType + | QFileSystemMetaData::SequentialType + | QFileSystemMetaData::ExistsAttribute; + + entryFlags = QFileSystemMetaData::SequentialType + | QFileSystemMetaData::ExistsAttribute; + + break; + + case DT_LNK: + knownFlagsMask = QFileSystemMetaData::LinkType; + entryFlags = QFileSystemMetaData::LinkType; + break; + + case DT_REG: + knownFlagsMask = QFileSystemMetaData::LinkType + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::BundleType + | QFileSystemMetaData::SequentialType + | QFileSystemMetaData::ExistsAttribute; + + entryFlags = QFileSystemMetaData::FileType + | QFileSystemMetaData::ExistsAttribute; + + break; + + case DT_UNKNOWN: + default: + clear(); + } +#else + Q_UNUSED(entry) +#endif +} + +#endif + +//static +QString QFileSystemEngine::resolveUserName(const QFileSystemEntry &entry, QFileSystemMetaData &metaData) +{ +#if defined (Q_OS_SYMBIAN) + Q_UNUSED(entry); + Q_UNUSED(metaData); + return QString(); +#elif defined(Q_OS_WIN) + Q_UNUSED(metaData); + return QFileSystemEngine::owner(entry, QAbstractFileEngine::OwnerUser); +#else //(Q_OS_UNIX) + if (!metaData.hasFlags(QFileSystemMetaData::UserId)) + QFileSystemEngine::fillMetaData(entry, metaData, QFileSystemMetaData::UserId); + return resolveUserName(metaData.userId()); +#endif +} + +//static +QString QFileSystemEngine::resolveGroupName(const QFileSystemEntry &entry, QFileSystemMetaData &metaData) +{ +#if defined (Q_OS_SYMBIAN) + Q_UNUSED(entry); + Q_UNUSED(metaData); + return QString(); +#elif defined(Q_OS_WIN) + Q_UNUSED(metaData); + return QFileSystemEngine::owner(entry, QAbstractFileEngine::OwnerGroup); +#else //(Q_OS_UNIX) + if (!metaData.hasFlags(QFileSystemMetaData::GroupId)) + QFileSystemEngine::fillMetaData(entry, metaData, QFileSystemMetaData::GroupId); + return resolveGroupName(metaData.groupId()); +#endif +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemengine_mac.cpp b/src/corelib/io/qfilesystemengine_mac.cpp new file mode 100644 index 0000000000..1c0056bff4 --- /dev/null +++ b/src/corelib/io/qfilesystemengine_mac.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemengine_p.h" + +QT_BEGIN_NAMESPACE + +// Mac-specific implementations only! + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h new file mode 100644 index 0000000000..d6033e66e7 --- /dev/null +++ b/src/corelib/io/qfilesystemengine_p.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMENGINE_P_H_INCLUDED +#define QFILESYSTEMENGINE_P_H_INCLUDED + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qfile.h" +#include "qfilesystementry_p.h" +#include "qfilesystemmetadata_p.h" +#include <QtCore/private/qsystemerror_p.h> + +QT_BEGIN_NAMESPACE + +class QFileSystemEngine +{ +public: + static bool isCaseSensitive(); + + static QFileSystemEntry getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data); + static QFileSystemEntry canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data); + static QFileSystemEntry absoluteName(const QFileSystemEntry &entry); + static QString resolveUserName(const QFileSystemEntry &entry, QFileSystemMetaData &data); + static QString resolveGroupName(const QFileSystemEntry &entry, QFileSystemMetaData &data); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN) + static QString resolveUserName(uint userId); + static QString resolveGroupName(uint groupId); +#endif + +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) + static QString bundleName(const QFileSystemEntry &entry); +#else + static QString bundleName(const QFileSystemEntry &entry) { Q_UNUSED(entry) return QString(); } +#endif + + static bool fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, + QFileSystemMetaData::MetaDataFlags what); +#if defined(Q_OS_UNIX) + static bool fillMetaData(int fd, QFileSystemMetaData &data); // what = PosixStatFlags +#endif +#if defined(Q_OS_WIN) + + static bool uncListSharesOnServer(const QString &server, QStringList *list); //Used also by QFSFileEngineIterator::hasNext() + static bool fillMetaData(int fd, QFileSystemMetaData &data, + QFileSystemMetaData::MetaDataFlags what); + static bool fillMetaData(HANDLE fHandle, QFileSystemMetaData &data, + QFileSystemMetaData::MetaDataFlags what); + static bool fillPermissions(const QFileSystemEntry &entry, QFileSystemMetaData &data, + QFileSystemMetaData::MetaDataFlags what); + static QString owner(const QFileSystemEntry &entry, QAbstractFileEngine::FileOwner own); + static QString nativeAbsoluteFilePath(const QString &path); +#endif + //homePath, rootPath and tempPath shall return clean paths + static QString homePath(); + static QString rootPath(); + static QString tempPath(); + + static bool createDirectory(const QFileSystemEntry &entry, bool createParents); + static bool removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents); + + static bool createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error); + + static bool copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error); + static bool renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error); + static bool removeFile(const QFileSystemEntry &entry, QSystemError &error); + + static bool setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, + QFileSystemMetaData *data = 0); + + static bool setCurrentPath(const QFileSystemEntry &entry); + static QFileSystemEntry currentPath(); + + static QAbstractFileEngine *resolveEntryAndCreateLegacyEngine(QFileSystemEntry &entry, + QFileSystemMetaData &data); +private: + static QString slowCanonicalized(const QString &path); +#if defined(Q_OS_WIN) + static void clearWinStatData(QFileSystemMetaData &data); +#endif +}; + +QT_END_NAMESPACE + +#endif // include guard diff --git a/src/corelib/io/qfilesystemengine_symbian.cpp b/src/corelib/io/qfilesystemengine_symbian.cpp new file mode 100644 index 0000000000..41a550aa1f --- /dev/null +++ b/src/corelib/io/qfilesystemengine_symbian.cpp @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemengine_p.h" +#include "qfsfileengine.h" +#include <QtCore/private/qcore_symbian_p.h> +#include <QtCore/qcoreapplication.h> + +#include <f32file.h> +#include <pathinfo.h> +#include <wchar.h> + +QT_BEGIN_NAMESPACE + +bool QFileSystemEngine::isCaseSensitive() +{ + return false; +} + +//TODO: resolve this with QDir::cleanPath, without breaking the behaviour of that +//function which is documented only by autotest +//input: a dirty absolute path, e.g. c:/../../foo/./ +//output: a clean absolute path, e.g. c:/foo/ +static QString symbianCleanAbsolutePath(const QString& path) +{ + bool isDir = path.endsWith(QLatin1Char('/')); + //using SkipEmptyParts flag to eliminate duplicated slashes + QStringList components = path.split(QLatin1Char('/'), QString::SkipEmptyParts); + int cdups = 0; + for(int i=components.count() - 1; i>=0; --i) { + if(components.at(i) == QLatin1String("..")) { + components.removeAt(i); + cdups++; + } + else if(components.at(i) == QLatin1String(".")) { + components.removeAt(i); + } + else if(cdups && i > 0) { + --cdups; + components.removeAt(i); + } + } + QString result = components.join(QLatin1String("/")); + if ((isDir&& !result.endsWith(QLatin1Char('/'))) + || (result.length() == 2 && result.at(1).unicode() == ':')) + result.append(QLatin1Char('/')); + return result; +} + +//static +QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) +{ + Q_UNUSED(data); + return link; +} + +//static +QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) +{ + if (entry.isEmpty() || entry.isRoot()) + return entry; + + QFileSystemEntry result = absoluteName(entry); + if (!data.hasFlags(QFileSystemMetaData::ExistsAttribute)) + fillMetaData(result, data, QFileSystemMetaData::ExistsAttribute); + if (!data.exists()) { + // file doesn't exist + return QFileSystemEntry(); + } else { + return result; + } +} + +//static +QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) +{ + QString orig = entry.filePath(); + const bool isAbsolute = entry.isAbsolute(); + const bool isDirty = (orig.contains(QLatin1String("/../")) || orig.contains(QLatin1String("/./")) || + orig.contains(QLatin1String("//")) || + orig.endsWith(QLatin1String("/..")) || orig.endsWith(QLatin1String("/."))); + if (isAbsolute && !isDirty) + return entry; + + const bool isRelative = entry.isRelative(); + const bool needsDrive = (!orig.isEmpty() && orig.at(0).unicode() == '/'); + const bool isDriveLetter = !needsDrive && !isAbsolute && !isRelative && orig.length() == 2; + const bool isDriveRelative = !needsDrive && !isAbsolute && !isRelative && orig.length() > 2; + + QString result; + if (needsDrive || isDriveLetter || isDriveRelative || !isAbsolute || orig.isEmpty()) { + QFileSystemEntry cur(currentPath()); + if(needsDrive) + result = cur.filePath().left(2); + else if(isDriveRelative && cur.filePath().at(0) != orig.at(0)) + result = orig.left(2); // for BC, see tst_QFileInfo::absolutePath(<not current drive>:my.dll) + else + result = cur.filePath(); + if(isDriveLetter) { + result[0] = orig.at(0); //copy drive letter + orig.clear(); + } + if(isDriveRelative) { + orig = orig.mid(2); //discard the drive specifier from orig + } + } + if (!orig.isEmpty() && !(orig.length() == 1 && orig.at(0).unicode() == '.')) { + if (!result.isEmpty() && !result.endsWith(QLatin1Char('/'))) + result.append(QLatin1Char('/')); + result.append(orig); + } + + return QFileSystemEntry(symbianCleanAbsolutePath(result), QFileSystemEntry::FromInternalPath()); +} + +void QFileSystemMetaData::fillFromTEntry(const TEntry& entry) +{ + entryFlags &= ~(QFileSystemMetaData::SymbianTEntryFlags); + knownFlagsMask |= QFileSystemMetaData::SymbianTEntryFlags; + //Symbian doesn't have unix type file permissions + entryFlags |= QFileSystemMetaData::ReadPermissions; + if(!entry.IsReadOnly()) { + entryFlags |= QFileSystemMetaData::WritePermissions; + } + //set the type + if(entry.IsDir()) + entryFlags |= (QFileSystemMetaData::DirectoryType | QFileSystemMetaData::ExecutePermissions); + else + entryFlags |= QFileSystemMetaData::FileType; + + //set the attributes + entryFlags |= QFileSystemMetaData::ExistsAttribute; + if(entry.IsHidden()) + entryFlags |= QFileSystemMetaData::HiddenAttribute; + +#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API + size_ = entry.FileSize(); +#else + size_ = (TUint)(entry.iSize); +#endif + + modificationTime_ = entry.iModified; +} + +void QFileSystemMetaData::fillFromVolumeInfo(const TVolumeInfo& info) +{ + entryFlags &= ~(QFileSystemMetaData::SymbianTEntryFlags); + knownFlagsMask |= QFileSystemMetaData::SymbianTEntryFlags; + entryFlags |= QFileSystemMetaData::ExistsAttribute; + entryFlags |= QFileSystemMetaData::Permissions; + if(info.iDrive.iDriveAtt & KDriveAttRom) { + entryFlags &= ~(QFileSystemMetaData::WritePermissions); + } + entryFlags |= QFileSystemMetaData::DirectoryType; + size_ = info.iSize; + modificationTime_ = qt_symbian_time_t_To_TTime(0); +} + +//static +bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, QFileSystemMetaData::MetaDataFlags what) +{ + if (what & QFileSystemMetaData::SymbianTEntryFlags) { + RFs& fs(qt_s60GetRFs()); + TInt err; + QFileSystemEntry absentry(absoluteName(entry)); + if (entry.isEmpty()) { + err = KErrNotFound; + } else if (absentry.isRoot()) { + //Root directories don't have an entry, and Entry() returns KErrBadName. + //Therefore get information about the volume instead. + TInt drive; + err = RFs::CharToDrive(TChar(absentry.nativeFilePath().at(0).unicode()), drive); + if (!err) { + TVolumeInfo info; + err = fs.Volume(info, drive); + if (!err) + data.fillFromVolumeInfo(info); + } + } else { + TEntry ent; + err = fs.Entry(qt_QString2TPtrC(absentry.nativeFilePath()), ent); + if (!err) + data.fillFromTEntry(ent); + } + if (err) { + data.size_ = 0; + data.modificationTime_ = TTime(0); + data.entryFlags &= ~(QFileSystemMetaData::SymbianTEntryFlags); + } + //files in /sys/bin on any drive are executable, even though we don't normally have permission to check whether they exist or not + if(absentry.filePath().midRef(1,10).compare(QLatin1String(":/sys/bin/"), Qt::CaseInsensitive) == 0) + data.entryFlags |= QFileSystemMetaData::ExecutePermissions; + } + return data.hasFlags(what); +} + +//static +bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents) +{ + QString abspath = absoluteName(entry).nativeFilePath(); + if (!abspath.endsWith(QLatin1Char('\\'))) + abspath.append(QLatin1Char('\\')); + TInt r; + if (createParents) + r = qt_s60GetRFs().MkDirAll(qt_QString2TPtrC(abspath)); + else + r = qt_s60GetRFs().MkDir(qt_QString2TPtrC(abspath)); + if (createParents && r == KErrAlreadyExists) + return true; //# Qt5 - QDir::mkdir returns false for existing dir, QDir::mkpath returns true (should be made consistent in Qt 5) + return (r == KErrNone); +} + +//static +bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents) +{ + QString abspath = absoluteName(entry).nativeFilePath(); + if (!abspath.endsWith(QLatin1Char('\\'))) + abspath.append(QLatin1Char('\\')); + TPtrC dir(qt_QString2TPtrC(abspath)); + RFs& fs = qt_s60GetRFs(); + bool ok = false; + //behaviour of FS file engine: + //returns true if the directory could be removed + //success/failure of removing parent directories does not matter + while (KErrNone == fs.RmDir(dir)) { + ok = true; + if (!removeEmptyParents) + break; + //RFs::RmDir treats "c:\foo\bar" and "c:\foo\" the same, so it is sufficient to remove the last \ to the end + dir.Set(dir.Left(dir.LocateReverse(TChar('\\')))); + } + return ok; +} + +//static +bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +{ + Q_UNUSED(source) + Q_UNUSED(target) + error = QSystemError(KErrNotSupported, QSystemError::NativeError); + return false; +} + +//static +bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +{ + //CFileMan is allocated each time because it is not thread-safe + CFileMan *fm = 0; + TRAPD(err, fm = CFileMan::NewL(qt_s60GetRFs())); + if (err == KErrNone) { + err = fm->Copy(qt_QString2TPtrC(absoluteName(source).nativeFilePath()), qt_QString2TPtrC(absoluteName(target).nativeFilePath()), 0); + delete fm; + } + if (err == KErrNone) + return true; + error = QSystemError(err, QSystemError::NativeError); + return false; +} + +//static +bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +{ + QString sourcepath = absoluteName(source).nativeFilePath(); + QString targetpath = absoluteName(target).nativeFilePath(); + RFs& fs(qt_s60GetRFs()); + TInt err = fs.Rename(qt_QString2TPtrC(sourcepath), qt_QString2TPtrC(targetpath)); + if (err == KErrNone) + return true; + error = QSystemError(err, QSystemError::NativeError); + return false; +} + +//static +bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error) +{ + QString targetpath = absoluteName(entry).nativeFilePath(); + RFs& fs(qt_s60GetRFs()); + TInt err = fs.Delete(qt_QString2TPtrC(targetpath)); + if (err == KErrNone) + return true; + error = QSystemError(err, QSystemError::NativeError); + return false; +} + +//static +bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data) +{ + QString targetpath = absoluteName(entry).nativeFilePath(); + TUint setmask = 0; + TUint clearmask = 0; + RFs& fs(qt_s60GetRFs()); + if (permissions & (QFile::WriteOwner | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther)) + clearmask = KEntryAttReadOnly; //if anyone can write, it's not read-only + else + setmask = KEntryAttReadOnly; + TInt err = fs.SetAtt(qt_QString2TPtrC(targetpath), setmask, clearmask); + if (data && !err) { + data->entryFlags &= ~QFileSystemMetaData::Permissions; + data->entryFlags |= QFileSystemMetaData::MetaDataFlag(uint(permissions)); + data->knownFlagsMask |= QFileSystemMetaData::Permissions; + } + if (err == KErrNone) + return true; + error = QSystemError(err, QSystemError::NativeError); + return false; +} + +QString QFileSystemEngine::homePath() +{ + QString home = QDir::fromNativeSeparators(qt_TDesC2QString(PathInfo::PhoneMemoryRootPath())); + if(home.endsWith(QLatin1Char('/'))) + home.chop(1); + return home; +} + +QString QFileSystemEngine::rootPath() +{ + TChar drive; + TInt err = RFs::DriveToChar(RFs::GetSystemDrive(), drive); //RFs::GetSystemDriveChar not supported on S60 3.1 + Q_ASSERT(err == KErrNone); //RFs::GetSystemDrive() shall always return a convertible drive number on a valid OS configuration + return QString(QChar(drive)).append(QLatin1String(":/")); +} + +QString QFileSystemEngine::tempPath() +{ + return rootPath().append(QLatin1String("system/temp")); +} + +//static +bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &entry) +{ + QFileSystemMetaData meta; + QFileSystemEntry absname = absoluteName(entry); + fillMetaData(absname, meta, QFileSystemMetaData::ExistsAttribute | QFileSystemMetaData::DirectoryType); + if(!(meta.exists() && meta.isDirectory())) + return false; + + RFs& fs = qt_s60GetRFs(); + QString abspath = absname.nativeFilePath(); + if(!abspath.endsWith(QLatin1Char('\\'))) + abspath.append(QLatin1Char('\\')); + TInt r = fs.SetSessionPath(qt_QString2TPtrC(abspath)); + //SetSessionPath succeeds for non existent directory, which is why it's checked above + if (r == KErrNone) { + __ASSERT_COMPILE(sizeof(wchar_t) == sizeof(unsigned short)); + //attempt to set open C to the same path + r = ::wchdir(reinterpret_cast<const wchar_t *>(absname.filePath().utf16())); + if (r < 0) + qWarning("failed to sync path to open C"); + return true; + } + return false; +} + +//static +QFileSystemEntry QFileSystemEngine::currentPath() +{ + TFileName fn; + QFileSystemEntry ret; + TInt r = qt_s60GetRFs().SessionPath(fn); + if(r == KErrNone) { + //remove terminating slash from non root paths (session path is clean, absolute and always ends in a \) + if(fn.Length() > 3 && fn[fn.Length() - 1] == '\\') + fn.SetLength(fn.Length() - 1); + ret = QFileSystemEntry(qt_TDesC2QString(fn), QFileSystemEntry::FromNativePath()); + } + return ret; +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp new file mode 100644 index 0000000000..c9ebaa48bc --- /dev/null +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -0,0 +1,660 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qfilesystemengine_p.h" +#include "qplatformdefs.h" +#include "qfsfileengine.h" +#include "qfile.h" + +#include <QtCore/qvarlengtharray.h> + +#include <stdlib.h> // for realpath() +#include <unistd.h> +#include <stdio.h> +#include <errno.h> + + +#if defined(Q_OS_MAC) +# include <QtCore/private/qcore_mac_p.h> +#endif + +QT_BEGIN_NAMESPACE + +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) +static inline bool _q_isMacHidden(const char *nativePath) +{ + OSErr err; + + FSRef fsRef; + err = FSPathMakeRefWithOptions(reinterpret_cast<const UInt8 *>(nativePath), + kFSPathMakeRefDoNotFollowLeafSymlink, &fsRef, 0); + if (err != noErr) + return false; + + FSCatalogInfo catInfo; + err = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catInfo, NULL, NULL, NULL); + if (err != noErr) + return false; + + FileInfo * const fileInfo = reinterpret_cast<FileInfo*>(&catInfo.finderInfo); + return (fileInfo->finderFlags & kIsInvisible); +} +#else +static inline bool _q_isMacHidden(const char *nativePath) +{ + Q_UNUSED(nativePath); + // no-op + return false; +} +#endif + +bool QFileSystemEngine::isCaseSensitive() +{ + return true; +} + +//static +QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) +{ +#if defined(__GLIBC__) && !defined(PATH_MAX) +#define PATH_CHUNK_SIZE 256 + char *s = 0; + int len = -1; + int size = PATH_CHUNK_SIZE; + + while (1) { + s = (char *) ::realloc(s, size); + Q_CHECK_PTR(s); + len = ::readlink(link.nativeFilePath().constData(), s, size); + if (len < 0) { + ::free(s); + break; + } + if (len < size) { + break; + } + size *= 2; + } +#else + char s[PATH_MAX+1]; + int len = readlink(link.nativeFilePath().constData(), s, PATH_MAX); +#endif + if (len > 0) { + QString ret; + if (!data.hasFlags(QFileSystemMetaData::DirectoryType)) + fillMetaData(link, data, QFileSystemMetaData::DirectoryType); + if (data.isDirectory() && s[0] != '/') { + QDir parent(link.filePath()); + parent.cdUp(); + ret = parent.path(); + if (!ret.isEmpty() && !ret.endsWith(QLatin1Char('/'))) + ret += QLatin1Char('/'); + } + s[len] = '\0'; + ret += QFile::decodeName(QByteArray(s)); +#if defined(__GLIBC__) && !defined(PATH_MAX) + ::free(s); +#endif + + if (!ret.startsWith(QLatin1Char('/'))) { + if (link.filePath().startsWith(QLatin1Char('/'))) { + ret.prepend(link.filePath().left(link.filePath().lastIndexOf(QLatin1Char('/'))) + + QLatin1Char('/')); + } else { + ret.prepend(QDir::currentPath() + QLatin1Char('/')); + } + } + ret = QDir::cleanPath(ret); + if (ret.size() > 1 && ret.endsWith(QLatin1Char('/'))) + ret.chop(1); + return QFileSystemEntry(ret); + } +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) + { + FSRef fref; + if (FSPathMakeRef((const UInt8 *)QFile::encodeName(QDir::cleanPath(link.filePath())).data(), &fref, 0) == noErr) { + // TODO get the meta data info from the QFileSystemMetaData object + Boolean isAlias, isFolder; + if (FSResolveAliasFile(&fref, true, &isFolder, &isAlias) == noErr && isAlias) { + AliasHandle alias; + if (FSNewAlias(0, &fref, &alias) == noErr && alias) { + QCFString cfstr; + if (FSCopyAliasInfo(alias, 0, 0, &cfstr, 0, 0) == noErr) + return QFileSystemEntry(QCFString::toQString(cfstr)); + } + } + } + } +#endif + return QFileSystemEntry(); +} + +//static +QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) +{ + if (entry.isEmpty() || entry.isRoot()) + return entry; + +#if !defined(Q_OS_MAC) && _POSIX_VERSION < 200809L + // realpath(X,0) is not supported + Q_UNUSED(data); + return QFileSystemEntry(slowCanonicalized(absoluteName(entry).filePath())); +#else + char *ret = 0; +# if defined(Q_OS_MAC) && !defined(QT_NO_CORESERVICES) + // Mac OS X 10.5.x doesn't support the realpath(X,0) extension we use here. + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_6) { + ret = realpath(entry.nativeFilePath().constData(), (char*)0); + } else { + // on 10.5 we can use FSRef to resolve the file path. + QString path = QDir::cleanPath(entry.filePath()); + FSRef fsref; + if (FSPathMakeRef((const UInt8 *)path.toUtf8().data(), &fsref, 0) == noErr) { + CFURLRef urlref = CFURLCreateFromFSRef(NULL, &fsref); + CFStringRef canonicalPath = CFURLCopyFileSystemPath(urlref, kCFURLPOSIXPathStyle); + QString ret = QCFString::toQString(canonicalPath); + CFRelease(canonicalPath); + CFRelease(urlref); + return QFileSystemEntry(ret); + } + } +# else + ret = realpath(entry.nativeFilePath().constData(), (char*)0); +# endif + if (ret) { + data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute; + data.entryFlags |= QFileSystemMetaData::ExistsAttribute; + QString canonicalPath = QDir::cleanPath(QString::fromLocal8Bit(ret)); + free(ret); + return QFileSystemEntry(canonicalPath); + } else if (errno == ENOENT) { // file doesn't exist + data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute; + data.entryFlags &= ~(QFileSystemMetaData::ExistsAttribute); + return QFileSystemEntry(); + } + return entry; +#endif +} + +//static +QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) +{ + if (entry.isAbsolute()) + return entry; + + QByteArray orig = entry.nativeFilePath(); + QByteArray result; + if (orig.isEmpty() || !orig.startsWith('/')) { + QFileSystemEntry cur(currentPath()); + result = cur.nativeFilePath(); + } + if (!orig.isEmpty() && !(orig.length() == 1 && orig[0] == '.')) { + if (!result.isEmpty() && !result.endsWith('/')) + result.append('/'); + result.append(orig); + } + + if (result.length() == 1 && result[0] == '/') + return QFileSystemEntry(result, QFileSystemEntry::FromNativePath()); + const bool isDir = result.endsWith('/'); + + /* as long as QDir::cleanPath() operates on a QString we have to convert to a string here. + * ideally we never convert to a string since that loses information. Please fix after + * we get a QByteArray version of QDir::cleanPath() + */ + QFileSystemEntry resultingEntry(result, QFileSystemEntry::FromNativePath()); + QString stringVersion = QDir::cleanPath(resultingEntry.filePath()); + if (isDir) + stringVersion.append(QLatin1Char('/')); + return QFileSystemEntry(stringVersion); +} + +//static +QString QFileSystemEngine::resolveUserName(uint userId) +{ +#if !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) + int size_max = sysconf(_SC_GETPW_R_SIZE_MAX); + if (size_max == -1) + size_max = 1024; + QVarLengthArray<char, 1024> buf(size_max); +#endif + + struct passwd *pw = 0; +#if !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) + struct passwd entry; + getpwuid_r(userId, &entry, buf.data(), buf.size(), &pw); +#else + pw = getpwuid(userId); +#endif + if (pw) + return QFile::decodeName(QByteArray(pw->pw_name)); + return QString(); +} + +//static +QString QFileSystemEngine::resolveGroupName(uint groupId) +{ +#if !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) + int size_max = sysconf(_SC_GETPW_R_SIZE_MAX); + if (size_max == -1) + size_max = 1024; + QVarLengthArray<char, 1024> buf(size_max); +#endif + + struct group *gr = 0; +#if !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) + size_max = sysconf(_SC_GETGR_R_SIZE_MAX); + if (size_max == -1) + size_max = 1024; + buf.resize(size_max); + struct group entry; + // Some large systems have more members than the POSIX max size + // Loop over by doubling the buffer size (upper limit 250k) + for (unsigned size = size_max; size < 256000; size += size) + { + buf.resize(size); + // ERANGE indicates that the buffer was too small + if (!getgrgid_r(groupId, &entry, buf.data(), buf.size(), &gr) + || errno != ERANGE) + break; + } +#else + gr = getgrgid(groupId); +#endif + if (gr) + return QFile::decodeName(QByteArray(gr->gr_name)); + return QString(); +} + +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) +//static +QString QFileSystemEngine::bundleName(const QFileSystemEntry &entry) +{ + QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, QCFString(entry.filePath()), + kCFURLPOSIXPathStyle, true); + if (QCFType<CFDictionaryRef> dict = CFBundleCopyInfoDictionaryForURL(url)) { + if (CFTypeRef name = (CFTypeRef)CFDictionaryGetValue(dict, kCFBundleNameKey)) { + if (CFGetTypeID(name) == CFStringGetTypeID()) + return QCFString::toQString((CFStringRef)name); + } + } + return QString(); +} +#endif + +//static +bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, + QFileSystemMetaData::MetaDataFlags what) +{ +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) + if (what & QFileSystemMetaData::BundleType) { + if (!data.hasFlags(QFileSystemMetaData::DirectoryType)) + what |= QFileSystemMetaData::DirectoryType; + } +#endif + +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + if (what & QFileSystemMetaData::HiddenAttribute) { + // Mac OS >= 10.5: st_flags & UF_HIDDEN + what |= QFileSystemMetaData::PosixStatFlags; + } +#endif + + if (what & QFileSystemMetaData::PosixStatFlags) + what |= QFileSystemMetaData::PosixStatFlags; + + if (what & QFileSystemMetaData::ExistsAttribute) { + // FIXME: Would other queries being performed provide this bit? + what |= QFileSystemMetaData::PosixStatFlags; + } + + data.entryFlags &= ~what; + + const char * nativeFilePath; + int nativeFilePathLength; + { + const QByteArray &path = entry.nativeFilePath(); + nativeFilePath = path.constData(); + nativeFilePathLength = path.size(); + } + + bool entryExists = true; // innocent until proven otherwise + + QT_STATBUF statBuffer; + bool statBufferValid = false; + if (what & QFileSystemMetaData::LinkType) { + if (QT_LSTAT(nativeFilePath, &statBuffer) == 0) { + if (S_ISLNK(statBuffer.st_mode)) { + data.entryFlags |= QFileSystemMetaData::LinkType; + } else { + statBufferValid = true; + data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags; + } + } else { + entryExists = false; + } + + data.knownFlagsMask |= QFileSystemMetaData::LinkType; + } + + if (statBufferValid || (what & QFileSystemMetaData::PosixStatFlags)) { + if (entryExists && !statBufferValid) + statBufferValid = (QT_STAT(nativeFilePath, &statBuffer) == 0); + + if (statBufferValid) + data.fillFromStatBuf(statBuffer); + else { + entryExists = false; + data.creationTime_ = 0; + data.modificationTime_ = 0; + data.accessTime_ = 0; + data.size_ = 0; + data.userId_ = (uint) -2; + data.groupId_ = (uint) -2; + } + + // reset the mask + data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags + | QFileSystemMetaData::ExistsAttribute; + } + +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) + if (what & QFileSystemMetaData::AliasType) + { + if (entryExists) { + FSRef fref; + if (FSPathMakeRef((const UInt8 *)nativeFilePath, &fref, NULL) == noErr) { + Boolean isAlias, isFolder; + if (FSIsAliasFile(&fref, &isAlias, &isFolder) == noErr) { + if (isAlias) + data.entryFlags |= QFileSystemMetaData::AliasType; + } + } + } + data.knownFlagsMask |= QFileSystemMetaData::AliasType; + } +#endif + + if (what & QFileSystemMetaData::UserPermissions) { + // calculate user permissions + + if (entryExists) { + if (what & QFileSystemMetaData::UserReadPermission) { + if (QT_ACCESS(nativeFilePath, R_OK) == 0) + data.entryFlags |= QFileSystemMetaData::UserReadPermission; + } + if (what & QFileSystemMetaData::UserWritePermission) { + if (QT_ACCESS(nativeFilePath, W_OK) == 0) + data.entryFlags |= QFileSystemMetaData::UserWritePermission; + } + if (what & QFileSystemMetaData::UserExecutePermission) { + if (QT_ACCESS(nativeFilePath, X_OK) == 0) + data.entryFlags |= QFileSystemMetaData::UserExecutePermission; + } + } + data.knownFlagsMask |= (what & QFileSystemMetaData::UserPermissions); + } + + if (what & QFileSystemMetaData::HiddenAttribute + && !data.isHidden()) { + QString fileName = entry.fileName(); + if ((fileName.size() > 0 && fileName.at(0) == QLatin1Char('.')) + || (entryExists && _q_isMacHidden(nativeFilePath))) + data.entryFlags |= QFileSystemMetaData::HiddenAttribute; + data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute; + } + +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) + if (what & QFileSystemMetaData::BundleType) { + if (entryExists && data.isDirectory()) { + QCFType<CFStringRef> path = CFStringCreateWithBytes(0, + (const UInt8*)nativeFilePath, nativeFilePathLength, + kCFStringEncodingUTF8, false); + QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, path, + kCFURLPOSIXPathStyle, true); + + UInt32 type, creator; + if (CFBundleGetPackageInfoInDirectory(url, &type, &creator)) + data.entryFlags |= QFileSystemMetaData::BundleType; + } + + data.knownFlagsMask |= QFileSystemMetaData::BundleType; + } +#endif + + return data.hasFlags(what); +} + +//static +bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents) +{ + QString dirName = entry.filePath(); + if (createParents) { + dirName = QDir::cleanPath(dirName); + for (int oldslash = -1, slash=0; slash != -1; oldslash = slash) { + slash = dirName.indexOf(QDir::separator(), oldslash+1); + if (slash == -1) { + if (oldslash == dirName.length()) + break; + slash = dirName.length(); + } + if (slash) { + QByteArray chunk = QFile::encodeName(dirName.left(slash)); + QT_STATBUF st; + if (QT_STAT(chunk, &st) != -1) { + if ((st.st_mode & S_IFMT) != S_IFDIR) + return false; + } else if (QT_MKDIR(chunk, 0777) != 0) { + return false; + } + } + } + return true; + } +#if defined(Q_OS_DARWIN) // Mac X doesn't support trailing /'s + if (dirName.endsWith(QLatin1Char('/'))) + dirName.chop(1); +#endif + return (QT_MKDIR(QFile::encodeName(dirName), 0777) == 0); +} + +//static +bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents) +{ + if (removeEmptyParents) { + QString dirName = QDir::cleanPath(entry.filePath()); + for (int oldslash = 0, slash=dirName.length(); slash > 0; oldslash = slash) { + QByteArray chunk = QFile::encodeName(dirName.left(slash)); + QT_STATBUF st; + if (QT_STAT(chunk, &st) != -1) { + if ((st.st_mode & S_IFMT) != S_IFDIR) + return false; + if (::rmdir(chunk) != 0) + return oldslash != 0; + } else { + return false; + } + slash = dirName.lastIndexOf(QDir::separator(), oldslash-1); + } + return true; + } + return rmdir(QFile::encodeName(entry.filePath())) == 0; +} + +//static +bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +{ + if (::symlink(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0) + return true; + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; +} + +//static +bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +{ + Q_UNUSED(source); + Q_UNUSED(target); + error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); //Function not implemented + return false; +} + +//static +bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +{ + if (::rename(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0) + return true; + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; +} + +//static +bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error) +{ + if (unlink(entry.nativeFilePath().constData()) == 0) + return true; + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; + +} + +//static +bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data) +{ + mode_t mode = 0; + if (permissions & QFile::ReadOwner) + mode |= S_IRUSR; + if (permissions & QFile::WriteOwner) + mode |= S_IWUSR; + if (permissions & QFile::ExeOwner) + mode |= S_IXUSR; + if (permissions & QFile::ReadUser) + mode |= S_IRUSR; + if (permissions & QFile::WriteUser) + mode |= S_IWUSR; + if (permissions & QFile::ExeUser) + mode |= S_IXUSR; + if (permissions & QFile::ReadGroup) + mode |= S_IRGRP; + if (permissions & QFile::WriteGroup) + mode |= S_IWGRP; + if (permissions & QFile::ExeGroup) + mode |= S_IXGRP; + if (permissions & QFile::ReadOther) + mode |= S_IROTH; + if (permissions & QFile::WriteOther) + mode |= S_IWOTH; + if (permissions & QFile::ExeOther) + mode |= S_IXOTH; + + bool success = ::chmod(entry.nativeFilePath().constData(), mode) == 0; + if (success && data) { + data->entryFlags &= ~QFileSystemMetaData::Permissions; + data->entryFlags |= QFileSystemMetaData::MetaDataFlag(uint(permissions)); + data->knownFlagsMask |= QFileSystemMetaData::Permissions; + } + if (!success) + error = QSystemError(errno, QSystemError::StandardLibraryError); + return success; +} + +QString QFileSystemEngine::homePath() +{ + QString home = QFile::decodeName(qgetenv("HOME")); + if (home.isNull()) + home = rootPath(); + return QDir::cleanPath(home); +} + +QString QFileSystemEngine::rootPath() +{ + return QLatin1String("/"); +} + +QString QFileSystemEngine::tempPath() +{ +#ifdef QT_UNIX_TEMP_PATH_OVERRIDE + return QLatin1String(QT_UNIX_TEMP_PATH_OVERRIDE); +#else + QString temp = QFile::decodeName(qgetenv("TMPDIR")); + if (temp.isEmpty()) + temp = QLatin1String("/tmp/"); + return QDir::cleanPath(temp); +#endif +} + +bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &path) +{ + int r; + r = QT_CHDIR(path.nativeFilePath()); + return r >= 0; +} + +QFileSystemEntry QFileSystemEngine::currentPath() +{ + QFileSystemEntry result; + QT_STATBUF st; + if (QT_STAT(".", &st) == 0) { +#if defined(__GLIBC__) && !defined(PATH_MAX) + char *currentName = ::get_current_dir_name(); + if (currentName) { + result = QFile::decodeName(QByteArray(currentName)); + ::free(currentName); + } +#else + char currentName[PATH_MAX+1]; + if (::getcwd(currentName, PATH_MAX)) + result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath()); +# if defined(QT_DEBUG) + if (result.isEmpty()) + qWarning("QFSFileEngine::currentPath: getcwd() failed"); +# endif +#endif + } else { +# if defined(QT_DEBUG) + qWarning("QFSFileEngine::currentPath: stat(\".\") failed"); +# endif + } + return result; +} +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp new file mode 100644 index 0000000000..82c6ebad05 --- /dev/null +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -0,0 +1,1218 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemengine_p.h" + +#define _POSIX_ +#include "qplatformdefs.h" +#include "qabstractfileengine.h" +#include "private/qfsfileengine_p.h" +#include <private/qsystemlibrary_p.h> +#include <qdebug.h> + +#include "qfile.h" +#include "qdir.h" +#include "private/qmutexpool_p.h" +#include "qvarlengtharray.h" +#include "qdatetime.h" +#include "qt_windows.h" + +#if !defined(Q_OS_WINCE) +# include <sys/types.h> +# include <direct.h> +# include <winioctl.h> +#else +# include <types.h> +#endif +#include <objbase.h> +#include <shlobj.h> +#include <initguid.h> +#include <accctrl.h> +#include <ctype.h> +#include <limits.h> +#define SECURITY_WIN32 +#include <security.h> + +#ifndef SPI_GETPLATFORMTYPE +#define SPI_GETPLATFORMTYPE 257 +#endif + +#ifndef PATH_MAX +#define PATH_MAX FILENAME_MAX +#endif + +#ifndef _INTPTR_T_DEFINED +#ifdef _WIN64 +typedef __int64 intptr_t; +#else +#ifdef _W64 +typedef _W64 int intptr_t; +#else +typedef INT_PTR intptr_t; +#endif +#endif +#define _INTPTR_T_DEFINED +#endif + +#ifndef INVALID_FILE_ATTRIBUTES +# define INVALID_FILE_ATTRIBUTES (DWORD (-1)) +#endif + +#if !defined(Q_OS_WINCE) +# if !defined(REPARSE_DATA_BUFFER_HEADER_SIZE) +typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; +# define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) +# endif // !defined(REPARSE_DATA_BUFFER_HEADER_SIZE) + +# ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE +# define MAXIMUM_REPARSE_DATA_BUFFER_SIZE 16384 +# endif +# ifndef IO_REPARSE_TAG_SYMLINK +# define IO_REPARSE_TAG_SYMLINK (0xA000000CL) +# endif +# ifndef FSCTL_GET_REPARSE_POINT +# define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) +# endif +#endif // !defined(Q_OS_WINCE) + +QT_BEGIN_NAMESPACE + +Q_CORE_EXPORT int qt_ntfs_permission_lookup = 0; + +#if defined(Q_OS_WINCE) +static QString qfsPrivateCurrentDir = QLatin1String(""); +// As none of the functions we try to resolve do exist on Windows CE +// we use QT_NO_LIBRARY to shorten everything up a little bit. +#define QT_NO_LIBRARY 1 +#endif + +#if !defined(QT_NO_LIBRARY) +QT_BEGIN_INCLUDE_NAMESPACE +typedef DWORD (WINAPI *PtrGetNamedSecurityInfoW)(LPWSTR, SE_OBJECT_TYPE, SECURITY_INFORMATION, PSID*, PSID*, PACL*, PACL*, PSECURITY_DESCRIPTOR*); +static PtrGetNamedSecurityInfoW ptrGetNamedSecurityInfoW = 0; +typedef BOOL (WINAPI *PtrLookupAccountSidW)(LPCWSTR, PSID, LPWSTR, LPDWORD, LPWSTR, LPDWORD, PSID_NAME_USE); +static PtrLookupAccountSidW ptrLookupAccountSidW = 0; +typedef VOID (WINAPI *PtrBuildTrusteeWithSidW)(PTRUSTEE_W, PSID); +static PtrBuildTrusteeWithSidW ptrBuildTrusteeWithSidW = 0; +typedef DWORD (WINAPI *PtrGetEffectiveRightsFromAclW)(PACL, PTRUSTEE_W, OUT PACCESS_MASK); +static PtrGetEffectiveRightsFromAclW ptrGetEffectiveRightsFromAclW = 0; +static TRUSTEE_W currentUserTrusteeW; +static TRUSTEE_W worldTrusteeW; + +typedef BOOL (WINAPI *PtrGetUserProfileDirectoryW)(HANDLE, LPWSTR, LPDWORD); +static PtrGetUserProfileDirectoryW ptrGetUserProfileDirectoryW = 0; +typedef BOOL (WINAPI *PtrGetVolumePathNamesForVolumeNameW)(LPCWSTR,LPWSTR,DWORD,PDWORD); +static PtrGetVolumePathNamesForVolumeNameW ptrGetVolumePathNamesForVolumeNameW = 0; +QT_END_INCLUDE_NAMESPACE + + +static void resolveLibs() +{ + static bool triedResolve = false; + if (!triedResolve) { + // need to resolve the security info functions + + // protect initialization +#ifndef QT_NO_THREAD + QMutexLocker locker(QMutexPool::globalInstanceGet(&triedResolve)); + // check triedResolve again, since another thread may have already + // done the initialization + if (triedResolve) { + // another thread did initialize the security function pointers, + // so we shouldn't do it again. + return; + } +#endif + + triedResolve = true; +#if !defined(Q_OS_WINCE) + HINSTANCE advapiHnd = QSystemLibrary::load(L"advapi32"); + if (advapiHnd) { + ptrGetNamedSecurityInfoW = (PtrGetNamedSecurityInfoW)GetProcAddress(advapiHnd, "GetNamedSecurityInfoW"); + ptrLookupAccountSidW = (PtrLookupAccountSidW)GetProcAddress(advapiHnd, "LookupAccountSidW"); + ptrBuildTrusteeWithSidW = (PtrBuildTrusteeWithSidW)GetProcAddress(advapiHnd, "BuildTrusteeWithSidW"); + ptrGetEffectiveRightsFromAclW = (PtrGetEffectiveRightsFromAclW)GetProcAddress(advapiHnd, "GetEffectiveRightsFromAclW"); + } + if (ptrBuildTrusteeWithSidW) { + // Create TRUSTEE for current user + HANDLE hnd = ::GetCurrentProcess(); + HANDLE token = 0; + if (::OpenProcessToken(hnd, TOKEN_QUERY, &token)) { + TOKEN_USER tu; + DWORD retsize; + if (::GetTokenInformation(token, TokenUser, &tu, sizeof(tu), &retsize)) + ptrBuildTrusteeWithSidW(¤tUserTrusteeW, tu.User.Sid); + ::CloseHandle(token); + } + + typedef BOOL (WINAPI *PtrAllocateAndInitializeSid)(PSID_IDENTIFIER_AUTHORITY, BYTE, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, PSID*); + PtrAllocateAndInitializeSid ptrAllocateAndInitializeSid = (PtrAllocateAndInitializeSid)GetProcAddress(advapiHnd, "AllocateAndInitializeSid"); + typedef PVOID (WINAPI *PtrFreeSid)(PSID); + PtrFreeSid ptrFreeSid = (PtrFreeSid)GetProcAddress(advapiHnd, "FreeSid"); + if (ptrAllocateAndInitializeSid && ptrFreeSid) { + // Create TRUSTEE for Everyone (World) + SID_IDENTIFIER_AUTHORITY worldAuth = { SECURITY_WORLD_SID_AUTHORITY }; + PSID pWorld = 0; + if (ptrAllocateAndInitializeSid(&worldAuth, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pWorld)) + ptrBuildTrusteeWithSidW(&worldTrusteeW, pWorld); + ptrFreeSid(pWorld); + } + } + HINSTANCE userenvHnd = QSystemLibrary::load(L"userenv"); + if (userenvHnd) + ptrGetUserProfileDirectoryW = (PtrGetUserProfileDirectoryW)GetProcAddress(userenvHnd, "GetUserProfileDirectoryW"); + HINSTANCE kernel32 = LoadLibrary(L"kernel32"); + if(kernel32) + ptrGetVolumePathNamesForVolumeNameW = (PtrGetVolumePathNamesForVolumeNameW)GetProcAddress(kernel32, "GetVolumePathNamesForVolumeNameW"); +#endif + } +} +#endif // QT_NO_LIBRARY + +typedef DWORD (WINAPI *PtrNetShareEnum)(LPWSTR, DWORD, LPBYTE*, DWORD, LPDWORD, LPDWORD, LPDWORD); +static PtrNetShareEnum ptrNetShareEnum = 0; +typedef DWORD (WINAPI *PtrNetApiBufferFree)(LPVOID); +static PtrNetApiBufferFree ptrNetApiBufferFree = 0; +typedef struct _SHARE_INFO_1 { + LPWSTR shi1_netname; + DWORD shi1_type; + LPWSTR shi1_remark; +} SHARE_INFO_1; + + +static bool resolveUNCLibs() +{ + static bool triedResolve = false; + if (!triedResolve) { +#ifndef QT_NO_THREAD + QMutexLocker locker(QMutexPool::globalInstanceGet(&triedResolve)); + if (triedResolve) { + return ptrNetShareEnum && ptrNetApiBufferFree; + } +#endif + triedResolve = true; +#if !defined(Q_OS_WINCE) + HINSTANCE hLib = QSystemLibrary::load(L"Netapi32"); + if (hLib) { + ptrNetShareEnum = (PtrNetShareEnum)GetProcAddress(hLib, "NetShareEnum"); + if (ptrNetShareEnum) + ptrNetApiBufferFree = (PtrNetApiBufferFree)GetProcAddress(hLib, "NetApiBufferFree"); + } +#endif + } + return ptrNetShareEnum && ptrNetApiBufferFree; +} + +static QString readSymLink(const QFileSystemEntry &link) +{ + QString result; +#if !defined(Q_OS_WINCE) + HANDLE handle = CreateFile((wchar_t*)link.nativeFilePath().utf16(), + FILE_READ_EA, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + 0); + if (handle != INVALID_HANDLE_VALUE) { + DWORD bufsize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE; + REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER*)qMalloc(bufsize); + DWORD retsize = 0; + if (::DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, 0, 0, rdb, bufsize, &retsize, 0)) { + if (rdb->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { + int length = rdb->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar_t); + int offset = rdb->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t); + const wchar_t* PathBuffer = &rdb->MountPointReparseBuffer.PathBuffer[offset]; + result = QString::fromWCharArray(PathBuffer, length); + } else if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + int length = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(wchar_t); + int offset = rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t); + const wchar_t* PathBuffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[offset]; + result = QString::fromWCharArray(PathBuffer, length); + } + // cut-off "//?/" and "/??/" + if (result.size() > 4 && result.at(0) == QLatin1Char('\\') && result.at(2) == QLatin1Char('?') && result.at(3) == QLatin1Char('\\')) + result = result.mid(4); + } + qFree(rdb); + CloseHandle(handle); + +#if !defined(QT_NO_LIBRARY) + resolveLibs(); + if (ptrGetVolumePathNamesForVolumeNameW) { + QRegExp matchVolName(QLatin1String("^Volume\\{([a-z]|[0-9]|-)+\\}\\\\"), Qt::CaseInsensitive); + if(matchVolName.indexIn(result) == 0) { + DWORD len; + wchar_t buffer[MAX_PATH]; + QString volumeName = result.mid(0, matchVolName.matchedLength()).prepend(QLatin1String("\\\\?\\")); + if(ptrGetVolumePathNamesForVolumeNameW((wchar_t*)volumeName.utf16(), buffer, MAX_PATH, &len) != 0) + result.replace(0,matchVolName.matchedLength(), QString::fromWCharArray(buffer)); + } + } +#endif + } +#else + Q_UNUSED(link); +#endif // Q_OS_WINCE + return result; +} + +static QString readLink(const QFileSystemEntry &link) +{ +#if !defined(Q_OS_WINCE) +#if !defined(QT_NO_LIBRARY) && !defined(Q_CC_MWERKS) + QString ret; + + bool neededCoInit = false; + IShellLink *psl; // pointer to IShellLink i/f + WIN32_FIND_DATA wfd; + wchar_t szGotPath[MAX_PATH]; + + // Get pointer to the IShellLink interface. + HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl); + + if (hres == CO_E_NOTINITIALIZED) { // COM was not initialized + neededCoInit = true; + CoInitialize(NULL); + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + IID_IShellLink, (LPVOID *)&psl); + } + if (SUCCEEDED(hres)) { // Get pointer to the IPersistFile interface. + IPersistFile *ppf; + hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf); + if (SUCCEEDED(hres)) { + hres = ppf->Load((LPOLESTR)link.nativeFilePath().utf16(), STGM_READ); + //The original path of the link is retrieved. If the file/folder + //was moved, the return value still have the old path. + if (SUCCEEDED(hres)) { + if (psl->GetPath(szGotPath, MAX_PATH, &wfd, SLGP_UNCPRIORITY) == NOERROR) + ret = QString::fromWCharArray(szGotPath); + } + ppf->Release(); + } + psl->Release(); + } + if (neededCoInit) + CoUninitialize(); + + return ret; +#else + Q_UNUSED(link); + return QString(); +#endif // QT_NO_LIBRARY +#else + wchar_t target[MAX_PATH]; + QString result; + if (SHGetShortcutTarget((wchar_t*)QFileInfo(link.filePath()).absoluteFilePath().replace(QLatin1Char('/'),QLatin1Char('\\')).utf16(), target, MAX_PATH)) { + result = QString::fromWCharArray(target); + if (result.startsWith(QLatin1Char('"'))) + result.remove(0,1); + if (result.endsWith(QLatin1Char('"'))) + result.remove(result.size()-1,1); + } + return result; +#endif // Q_OS_WINCE +} + +static bool uncShareExists(const QString &server) +{ + // This code assumes the UNC path is always like \\?\UNC\server... + QStringList parts = server.split(QLatin1Char('\\'), QString::SkipEmptyParts); + if (parts.count() >= 3) { + QStringList shares; + if (QFileSystemEngine::uncListSharesOnServer(QLatin1String("\\\\") + parts.at(2), &shares)) + return parts.count() >= 4 ? shares.contains(parts.at(3), Qt::CaseInsensitive) : true; + } + return false; +} + +static inline bool getFindData(QString path, WIN32_FIND_DATA &findData) +{ + // path should not end with a trailing slash + while (path.endsWith(QLatin1Char('\\'))) + path.chop(1); + + // can't handle drives + if (!path.endsWith(QLatin1Char(':'))) { + HANDLE hFind = ::FindFirstFile((wchar_t*)path.utf16(), &findData); + if (hFind != INVALID_HANDLE_VALUE) { + ::FindClose(hFind); + return true; + } + } + + return false; +} + +bool QFileSystemEngine::uncListSharesOnServer(const QString &server, QStringList *list) +{ + if (resolveUNCLibs()) { + SHARE_INFO_1 *BufPtr, *p; + DWORD res; + DWORD er = 0, tr = 0, resume = 0, i; + do { + res = ptrNetShareEnum((wchar_t*)server.utf16(), 1, (LPBYTE *)&BufPtr, DWORD(-1), &er, &tr, &resume); + if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA) { + p = BufPtr; + for (i = 1; i <= er; ++i) { + if (list && p->shi1_type == 0) + list->append(QString::fromWCharArray(p->shi1_netname)); + p++; + } + } + ptrNetApiBufferFree(BufPtr); + } while (res == ERROR_MORE_DATA); + return res == ERROR_SUCCESS; + } + return false; +} + +void QFileSystemEngine::clearWinStatData(QFileSystemMetaData &data) +{ + data.size_ = 0; + data.fileAttribute_ = 0; + data.creationTime_ = FILETIME(); + data.lastAccessTime_ = FILETIME(); + data.lastWriteTime_ = FILETIME(); +} + +bool QFileSystemEngine::isCaseSensitive() +{ + return false; +} + +//static +QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, + QFileSystemMetaData &data) +{ + if (data.missingFlags(QFileSystemMetaData::LinkType)) + QFileSystemEngine::fillMetaData(link, data, QFileSystemMetaData::LinkType); + + QString ret; + if (data.isLnkFile()) + ret = readLink(link); + else if (data.isLink()) + ret = readSymLink(link); + return QFileSystemEntry(ret); +} + +//static +QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) +{ + if (data.missingFlags(QFileSystemMetaData::ExistsAttribute)) + QFileSystemEngine::fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute); + + if (data.exists()) + return QFileSystemEntry(slowCanonicalized(absoluteName(entry).filePath())); + else + return QFileSystemEntry(); +} + +//static +QString QFileSystemEngine::nativeAbsoluteFilePath(const QString &path) +{ + // can be //server or //server/share + QString absPath; +#if !defined(Q_OS_WINCE) + QVarLengthArray<wchar_t, MAX_PATH> buf(qMax(MAX_PATH, path.size() + 1)); + wchar_t *fileName = 0; + DWORD retLen = GetFullPathName((wchar_t*)path.utf16(), buf.size(), buf.data(), &fileName); + if (retLen > (DWORD)buf.size()) { + buf.resize(retLen); + retLen = GetFullPathName((wchar_t*)path.utf16(), buf.size(), buf.data(), &fileName); + } + if (retLen != 0) + absPath = QString::fromWCharArray(buf.data(), retLen); +#else + if (path.startsWith(QLatin1Char('/')) || path.startsWith(QLatin1Char('\\'))) + absPath = QDir::toNativeSeparators(path); + else + absPath = QDir::toNativeSeparators(QDir::cleanPath(qfsPrivateCurrentDir + QLatin1Char('/') + path)); +#endif + // This is really ugly, but GetFullPathName strips off whitespace at the end. + // If you for instance write ". " in the lineedit of QFileDialog, + // (which is an invalid filename) this function will strip the space off and viola, + // the file is later reported as existing. Therefore, we re-add the whitespace that + // was at the end of path in order to keep the filename invalid. + if (!path.isEmpty() && path.at(path.size() - 1) == QLatin1Char(' ')) + absPath.append(QLatin1Char(' ')); + return absPath; +} + +//static +QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) +{ + QString ret; + + if (!entry.isRelative()) { +#if !defined(Q_OS_WINCE) + if (entry.isAbsolute() + && !entry.filePath().contains(QLatin1String("/../")) + && !entry.filePath().contains(QLatin1String("/./")) + && !entry.filePath().endsWith(QLatin1String("/..")) + && !entry.filePath().endsWith(QLatin1String("/."))) { + ret = entry.filePath(); + } else { + ret = QDir::fromNativeSeparators(nativeAbsoluteFilePath(entry.filePath())); + } +#else + ret = entry.filePath(); +#endif + } else { + ret = QDir::cleanPath(QDir::currentPath() + QLatin1Char('/') + entry.filePath()); + } + + // The path should be absolute at this point. + // From the docs : + // Absolute paths begin with the directory separator "/" + // (optionally preceded by a drive specification under Windows). + if (ret.at(0) != QLatin1Char('/')) { + Q_ASSERT(ret.length() >= 2); + Q_ASSERT(ret.at(0).isLetter()); + Q_ASSERT(ret.at(1) == QLatin1Char(':')); + + // Force uppercase drive letters. + ret[0] = ret.at(0).toUpper(); + } + return QFileSystemEntry(ret); +} + +//static +QString QFileSystemEngine::owner(const QFileSystemEntry &entry, QAbstractFileEngine::FileOwner own) +{ + QString name; +#if !defined(QT_NO_LIBRARY) + extern int qt_ntfs_permission_lookup; + if((qt_ntfs_permission_lookup > 0) && (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based)) { + resolveLibs(); + if (ptrGetNamedSecurityInfoW && ptrLookupAccountSidW) { + PSID pOwner = 0; + PSECURITY_DESCRIPTOR pSD; + if (ptrGetNamedSecurityInfoW((wchar_t*)entry.nativeFilePath().utf16(), SE_FILE_OBJECT, + own == QAbstractFileEngine::OwnerGroup ? GROUP_SECURITY_INFORMATION : OWNER_SECURITY_INFORMATION, + own == QAbstractFileEngine::OwnerUser ? &pOwner : 0, own == QAbstractFileEngine::OwnerGroup ? &pOwner : 0, + 0, 0, &pSD) == ERROR_SUCCESS) { + DWORD lowner = 64; + DWORD ldomain = 64; + QVarLengthArray<wchar_t, 64> owner(lowner); + QVarLengthArray<wchar_t, 64> domain(ldomain); + SID_NAME_USE use = SidTypeUnknown; + // First call, to determine size of the strings (with '\0'). + if (!ptrLookupAccountSidW(NULL, pOwner, (LPWSTR)owner.data(), &lowner, + (LPWSTR)domain.data(), &ldomain, (SID_NAME_USE*)&use)) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + if (lowner > (DWORD)owner.size()) + owner.resize(lowner); + if (ldomain > (DWORD)domain.size()) + domain.resize(ldomain); + // Second call, try on resized buf-s + if (!ptrLookupAccountSidW(NULL, pOwner, (LPWSTR)owner.data(), &lowner, + (LPWSTR)domain.data(), &ldomain, (SID_NAME_USE*)&use)) { + lowner = 0; + } + } else { + lowner = 0; + } + } + if (lowner != 0) + name = QString::fromWCharArray(owner.data()); + LocalFree(pSD); + } + } + } +#else + Q_UNUSED(own); +#endif + return name; +} + +//static +bool QFileSystemEngine::fillPermissions(const QFileSystemEntry &entry, QFileSystemMetaData &data, + QFileSystemMetaData::MetaDataFlags what) +{ + QAbstractFileEngine::FileFlags ret = 0; + +#if !defined(QT_NO_LIBRARY) + if((qt_ntfs_permission_lookup > 0) && (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based)) { + resolveLibs(); + if(ptrGetNamedSecurityInfoW && ptrBuildTrusteeWithSidW && ptrGetEffectiveRightsFromAclW) { + enum { ReadMask = 0x00000001, WriteMask = 0x00000002, ExecMask = 0x00000020 }; + + QString fname = entry.filePath(); + PSID pOwner = 0; + PSID pGroup = 0; + PACL pDacl; + PSECURITY_DESCRIPTOR pSD; + DWORD res = ptrGetNamedSecurityInfoW((wchar_t*)fname.utf16(), SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + &pOwner, &pGroup, &pDacl, 0, &pSD); + if(res == ERROR_SUCCESS) { + ACCESS_MASK access_mask; + TRUSTEE_W trustee; + if (what & QFileSystemMetaData::UserPermissions) { // user + data.knownFlagsMask |= QFileSystemMetaData::UserPermissions; + if(ptrGetEffectiveRightsFromAclW(pDacl, ¤tUserTrusteeW, &access_mask) != ERROR_SUCCESS) + access_mask = (ACCESS_MASK)-1; + if(access_mask & ReadMask) + data.entryFlags |= QFileSystemMetaData::UserReadPermission; + if(access_mask & WriteMask) + data.entryFlags|= QFileSystemMetaData::UserWritePermission; + if(access_mask & ExecMask) + data.entryFlags|= QFileSystemMetaData::UserExecutePermission; + } + if (what & QFileSystemMetaData::OwnerPermissions) { // owner + data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions; + ptrBuildTrusteeWithSidW(&trustee, pOwner); + if(ptrGetEffectiveRightsFromAclW(pDacl, &trustee, &access_mask) != ERROR_SUCCESS) + access_mask = (ACCESS_MASK)-1; + if(access_mask & ReadMask) + data.entryFlags |= QFileSystemMetaData::OwnerReadPermission; + if(access_mask & WriteMask) + data.entryFlags |= QFileSystemMetaData::OwnerWritePermission; + if(access_mask & ExecMask) + data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission; + } + if (what & QFileSystemMetaData::GroupPermissions) { // group + data.knownFlagsMask |= QFileSystemMetaData::GroupPermissions; + ptrBuildTrusteeWithSidW(&trustee, pGroup); + if(ptrGetEffectiveRightsFromAclW(pDacl, &trustee, &access_mask) != ERROR_SUCCESS) + access_mask = (ACCESS_MASK)-1; + if(access_mask & ReadMask) + data.entryFlags |= QFileSystemMetaData::GroupReadPermission; + if(access_mask & WriteMask) + data.entryFlags |= QFileSystemMetaData::GroupWritePermission; + if(access_mask & ExecMask) + data.entryFlags |= QFileSystemMetaData::GroupExecutePermission; + } + if (what & QFileSystemMetaData::OtherPermissions) { // other (world) + data.knownFlagsMask |= QFileSystemMetaData::OtherPermissions; + if(ptrGetEffectiveRightsFromAclW(pDacl, &worldTrusteeW, &access_mask) != ERROR_SUCCESS) + access_mask = (ACCESS_MASK)-1; // ### + if(access_mask & ReadMask) + data.entryFlags |= QFileSystemMetaData::OtherReadPermission; + if(access_mask & WriteMask) + data.entryFlags |= QFileSystemMetaData::OtherWritePermission; + if(access_mask & ExecMask) + data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission; + } + LocalFree(pSD); + } + } + } else +#endif + { + //### what to do with permissions if we don't use NTFS + // for now just add all permissions and what about exe missions ?? + // also qt_ntfs_permission_lookup is now not set by default ... should it ? + data.entryFlags |= QFileSystemMetaData::OwnerReadPermission + | QFileSystemMetaData::GroupReadPermission + | QFileSystemMetaData::OtherReadPermission; + + if (!(data.fileAttribute_ & FILE_ATTRIBUTE_READONLY)) { + data.entryFlags |= QFileSystemMetaData::OwnerWritePermission + | QFileSystemMetaData::GroupWritePermission + | QFileSystemMetaData::OtherWritePermission; + } + + QString fname = entry.filePath(); + QString ext = fname.right(4).toLower(); + if (data.isDirectory() || + ext == QLatin1String(".exe") || ext == QLatin1String(".com") || ext == QLatin1String(".bat") || + ext == QLatin1String(".pif") || ext == QLatin1String(".cmd")) { + data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission | QFileSystemMetaData::GroupExecutePermission + | QFileSystemMetaData::OtherExecutePermission | QFileSystemMetaData::UserExecutePermission; + } + data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions | QFileSystemMetaData::GroupPermissions + | QFileSystemMetaData::OtherPermissions | QFileSystemMetaData::UserExecutePermission; + // calculate user permissions + if (what & QFileSystemMetaData::UserReadPermission) { + if (::_waccess((wchar_t*)entry.nativeFilePath().utf16(), R_OK) == 0) + data.entryFlags |= QFileSystemMetaData::UserReadPermission; + data.knownFlagsMask |= QFileSystemMetaData::UserReadPermission; + } + if (what & QFileSystemMetaData::UserWritePermission) { + if (::_waccess((wchar_t*)entry.nativeFilePath().utf16(), W_OK) == 0) + data.entryFlags |= QFileSystemMetaData::UserWritePermission; + data.knownFlagsMask |= QFileSystemMetaData::UserReadPermission; + } + } + + return data.hasFlags(what); +} + +static bool tryDriveUNCFallback(const QFileSystemEntry &fname, QFileSystemMetaData &data) +{ + bool entryExists = false; + DWORD fileAttrib = 0; +#if !defined(Q_OS_WINCE) + if (fname.isDriveRoot()) { + // a valid drive ?? + DWORD drivesBitmask = ::GetLogicalDrives(); + int drivebit = 1 << (fname.filePath().at(0).toUpper().unicode() - QLatin1Char('A').unicode()); + if (drivesBitmask & drivebit) { + fileAttrib = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM; + entryExists = true; + } + } else { +#endif + const QString &path = fname.nativeFilePath(); + bool is_dir = false; + if (path.startsWith(QLatin1String("\\\\?\\UNC"))) { + // UNC - stat doesn't work for all cases (Windows bug) + int s = path.indexOf(path.at(0),7); + if (s > 0) { + // "\\?\UNC\server\..." + s = path.indexOf(path.at(0),s+1); + if (s > 0) { + // "\\?\UNC\server\share\..." + if (s == path.size() - 1) { + // "\\?\UNC\server\share\" + is_dir = true; + } else { + // "\\?\UNC\server\share\notfound" + } + } else { + // "\\?\UNC\server\share" + is_dir = true; + } + } else { + // "\\?\UNC\server" + is_dir = true; + } + } + if (is_dir && uncShareExists(path)) { + // looks like a UNC dir, is a dir. + fileAttrib = FILE_ATTRIBUTE_DIRECTORY; + entryExists = true; + } +#if !defined(Q_OS_WINCE) + } +#endif + if (entryExists) + data.fillFromFileAttribute(fileAttrib); + return entryExists; +} + +static bool tryFindFallback(const QFileSystemEntry &fname, QFileSystemMetaData &data) +{ + bool filledData = false; + // This assumes the last call to a Windows API failed. + int errorCode = GetLastError(); + if (errorCode == ERROR_ACCESS_DENIED || errorCode == ERROR_SHARING_VIOLATION) { + WIN32_FIND_DATA findData; + if (getFindData(fname.nativeFilePath(), findData) + && findData.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { + data.fillFromFindData(findData, true, fname.isDriveRoot()); + filledData = true; + } + } + return filledData; +} + +#if !defined(Q_OS_WINCE) +//static +bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data, + QFileSystemMetaData::MetaDataFlags what) +{ + HANDLE fHandle = (HANDLE)_get_osfhandle(fd); + if (fHandle != INVALID_HANDLE_VALUE) { + return fillMetaData(fHandle, data, what); + } + return false; +} +#endif + +//static +bool QFileSystemEngine::fillMetaData(HANDLE fHandle, QFileSystemMetaData &data, + QFileSystemMetaData::MetaDataFlags what) +{ + data.entryFlags &= ~what; + clearWinStatData(data); + BY_HANDLE_FILE_INFORMATION fileInfo; + UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); + if (GetFileInformationByHandle(fHandle , &fileInfo)) { + data.fillFromFindInfo(fileInfo); + } + SetErrorMode(oldmode); + return data.hasFlags(what); +} + +//static +bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, + QFileSystemMetaData::MetaDataFlags what) +{ + what |= QFileSystemMetaData::WinLnkType | QFileSystemMetaData::WinStatFlags; + data.entryFlags &= ~what; + + QFileSystemEntry fname; + data.knownFlagsMask |= QFileSystemMetaData::WinLnkType; + if(entry.filePath().endsWith(QLatin1String(".lnk"))) { + data.entryFlags |= QFileSystemMetaData::WinLnkType; + fname = QFileSystemEntry(readLink(entry)); + } else { + fname = entry; + } + + if (fname.isEmpty()) { + data.knownFlagsMask |= what; + clearWinStatData(data); + return false; + } + + if (what & QFileSystemMetaData::WinStatFlags) { + UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); + clearWinStatData(data); + WIN32_FIND_DATA findData; + // The memory structure for WIN32_FIND_DATA is same as WIN32_FILE_ATTRIBUTE_DATA + // for all members used by fillFindData(). + bool ok = ::GetFileAttributesEx((wchar_t*)fname.nativeFilePath().utf16(), GetFileExInfoStandard, + reinterpret_cast<WIN32_FILE_ATTRIBUTE_DATA *>(&findData)); + if (ok) { + data.fillFromFindData(findData, false, fname.isDriveRoot()); + } else { + if (!tryFindFallback(fname, data)) + tryDriveUNCFallback(fname, data); + } + SetErrorMode(oldmode); + } + + if (what & QFileSystemMetaData::Permissions) + fillPermissions(fname, data, what); + if ((what & QFileSystemMetaData::LinkType) + && data.missingFlags(QFileSystemMetaData::LinkType)) { + data.knownFlagsMask |= QFileSystemMetaData::LinkType; + if (data.fileAttribute_ & FILE_ATTRIBUTE_REPARSE_POINT) { + WIN32_FIND_DATA findData; + if (getFindData(fname.nativeFilePath(), findData)) + data.fillFromFindData(findData, true); + } + } + data.knownFlagsMask |= what; + return data.hasFlags(what); +} + +static inline bool mkDir(const QString &path) +{ +#if defined(Q_OS_WINCE) + // Unfortunately CreateDirectory returns true for paths longer than + // 256, but does not create a directory. It starts to fail, when + // path length > MAX_PATH, which is 260 usually on CE. + // This only happens on a Windows Mobile device. Windows CE seems + // not to be affected by this. + static int platformId = 0; + if (platformId == 0) { + wchar_t platformString[64]; + if (SystemParametersInfo(SPI_GETPLATFORMTYPE, sizeof(platformString)/sizeof(*platformString),platformString,0)) { + if (0 == wcscmp(platformString, L"PocketPC") || 0 == wcscmp(platformString, L"Smartphone")) + platformId = 1; + else + platformId = 2; + } + } + if (platformId == 1 && QFSFileEnginePrivate::longFileName(path).size() > 256) + return false; +#endif + return ::CreateDirectory((wchar_t*)QFSFileEnginePrivate::longFileName(path).utf16(), 0); +} + +static inline bool rmDir(const QString &path) +{ + return ::RemoveDirectory((wchar_t*)QFSFileEnginePrivate::longFileName(path).utf16()); +} + +static bool isDirPath(const QString &dirPath, bool *existed) +{ + QString path = dirPath; + if (path.length() == 2 && path.at(1) == QLatin1Char(':')) + path += QLatin1Char('\\'); + + DWORD fileAttrib = ::GetFileAttributes((wchar_t*)QFSFileEnginePrivate::longFileName(path).utf16()); + if (fileAttrib == INVALID_FILE_ATTRIBUTES) { + int errorCode = GetLastError(); + if (errorCode == ERROR_ACCESS_DENIED || errorCode == ERROR_SHARING_VIOLATION) { + WIN32_FIND_DATA findData; + if (getFindData(QFSFileEnginePrivate::longFileName(path), findData)) + fileAttrib = findData.dwFileAttributes; + } + } + + if (existed) + *existed = fileAttrib != INVALID_FILE_ATTRIBUTES; + + if (fileAttrib == INVALID_FILE_ATTRIBUTES) + return false; + + return fileAttrib & FILE_ATTRIBUTE_DIRECTORY; +} + +//static +bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents) +{ + QString dirName = entry.filePath(); + if (createParents) { + dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); + // We spefically search for / so \ would break it.. + int oldslash = -1; + if (dirName.startsWith(QLatin1String("\\\\"))) { + // Don't try to create the root path of a UNC path; + // CreateDirectory() will just return ERROR_INVALID_NAME. + for (int i = 0; i < dirName.size(); ++i) { + if (dirName.at(i) != QDir::separator()) { + oldslash = i; + break; + } + } + if (oldslash != -1) + oldslash = dirName.indexOf(QDir::separator(), oldslash); + } + for (int slash=0; slash != -1; oldslash = slash) { + slash = dirName.indexOf(QDir::separator(), oldslash+1); + if (slash == -1) { + if (oldslash == dirName.length()) + break; + slash = dirName.length(); + } + if (slash) { + QString chunk = dirName.left(slash); + bool existed = false; + if (!isDirPath(chunk, &existed)) { + if (!existed) { + if (!mkDir(chunk)) + return false; + } else { + return false; + } + } + } + } + return true; + } + return mkDir(entry.filePath()); +} + +//static +bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents) +{ + QString dirName = entry.filePath(); + if (removeEmptyParents) { + dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); + for (int oldslash = 0, slash=dirName.length(); slash > 0; oldslash = slash) { + QString chunk = dirName.left(slash); + if (chunk.length() == 2 && chunk.at(0).isLetter() && chunk.at(1) == QLatin1Char(':')) + break; + if (!isDirPath(chunk, 0)) + return false; + if (!rmDir(chunk)) + return oldslash != 0; + slash = dirName.lastIndexOf(QDir::separator(), oldslash-1); + } + return true; + } + return rmDir(entry.filePath()); +} + +//static +QString QFileSystemEngine::rootPath() +{ +#if defined(Q_OS_WINCE) + QString ret = QLatin1String("/"); +#elif defined(Q_FS_FAT) + QString ret = QString::fromLatin1(qgetenv("SystemDrive").constData()); + if (ret.isEmpty()) + ret = QLatin1String("c:"); + ret.append(QLatin1Char('/')); +#elif defined(Q_OS_OS2EMX) + char dir[4]; + _abspath(dir, QLatin1String("/"), _MAX_PATH); + QString ret(dir); +#endif + return ret; +} + +//static +QString QFileSystemEngine::homePath() +{ + QString ret; +#if !defined(QT_NO_LIBRARY) + resolveLibs(); + if (ptrGetUserProfileDirectoryW) { + HANDLE hnd = ::GetCurrentProcess(); + HANDLE token = 0; + BOOL ok = ::OpenProcessToken(hnd, TOKEN_QUERY, &token); + if (ok) { + DWORD dwBufferSize = 0; + // First call, to determine size of the strings (with '\0'). + ok = ptrGetUserProfileDirectoryW(token, NULL, &dwBufferSize); + if (!ok && dwBufferSize != 0) { // We got the required buffer size + wchar_t *userDirectory = new wchar_t[dwBufferSize]; + // Second call, now we can fill the allocated buffer. + ok = ptrGetUserProfileDirectoryW(token, userDirectory, &dwBufferSize); + if (ok) + ret = QString::fromWCharArray(userDirectory); + delete [] userDirectory; + } + ::CloseHandle(token); + } + } +#endif + if (ret.isEmpty() || !QFile::exists(ret)) { + ret = QString::fromLocal8Bit(qgetenv("USERPROFILE").constData()); + if (ret.isEmpty() || !QFile::exists(ret)) { + ret = QString::fromLocal8Bit(qgetenv("HOMEDRIVE").constData()) + + QString::fromLocal8Bit(qgetenv("HOMEPATH").constData()); + if (ret.isEmpty() || !QFile::exists(ret)) { + ret = QString::fromLocal8Bit(qgetenv("HOME").constData()); + if (ret.isEmpty() || !QFile::exists(ret)) { +#if defined(Q_OS_WINCE) + ret = QLatin1String("\\My Documents"); + if (!QFile::exists(ret)) +#endif + ret = rootPath(); + } + } + } + } + return QDir::fromNativeSeparators(ret); +} + +QString QFileSystemEngine::tempPath() +{ + QString ret; + wchar_t tempPath[MAX_PATH]; + DWORD len = GetTempPath(MAX_PATH, tempPath); + if (len) + ret = QString::fromWCharArray(tempPath, len); + if (!ret.isEmpty()) { + while (ret.endsWith(QLatin1Char('\\'))) + ret.chop(1); + ret = QDir::fromNativeSeparators(ret); + } + if (ret.isEmpty()) { +#if !defined(Q_OS_WINCE) + ret = QLatin1String("c:/tmp"); +#else + ret = QLatin1String("/Temp"); +#endif + } + return ret; +} + +bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &entry) +{ + QFileSystemMetaData meta; + fillMetaData(entry, meta, QFileSystemMetaData::ExistsAttribute | QFileSystemMetaData::DirectoryType); + if(!(meta.exists() && meta.isDirectory())) + return false; + +#if !defined(Q_OS_WINCE) + //TODO: this should really be using nativeFilePath(), but that returns a path in long format \\?\c:\foo + //which causes many problems later on when it's returned through currentPath() + return ::SetCurrentDirectory(reinterpret_cast<const wchar_t*>(QDir::toNativeSeparators(entry.filePath()).utf16())) != 0; +#else + qfsPrivateCurrentDir = entry.filePath(); + return true; +#endif +} + +QFileSystemEntry QFileSystemEngine::currentPath() +{ + QString ret; +#if !defined(Q_OS_WINCE) + DWORD size = 0; + wchar_t currentName[PATH_MAX]; + size = ::GetCurrentDirectory(PATH_MAX, currentName); + if (size != 0) { + if (size > PATH_MAX) { + wchar_t *newCurrentName = new wchar_t[size]; + if (::GetCurrentDirectory(PATH_MAX, newCurrentName) != 0) + ret = QString::fromWCharArray(newCurrentName, size); + delete [] newCurrentName; + } else { + ret = QString::fromWCharArray(currentName, size); + } + } + if (ret.length() >= 2 && ret[1] == QLatin1Char(':')) + ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters. +#else + Q_UNUSED(fileName); + //TODO - a race condition exists when using currentPath / setCurrentPath from multiple threads + if (qfsPrivateCurrentDir.isEmpty()) + qfsPrivateCurrentDir = QCoreApplication::applicationDirPath(); + + ret = qfsPrivateCurrentDir; +#endif + return QFileSystemEntry(ret, QFileSystemEntry::FromNativePath()); +} + +//static +bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +{ + Q_ASSERT(false); + Q_UNUSED(source) + Q_UNUSED(target) + Q_UNUSED(error) + + return false; // TODO implement; - code needs to be moved from qfsfileengine_win.cpp +} + +//static +bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +{ + bool ret = ::CopyFile((wchar_t*)source.nativeFilePath().utf16(), + (wchar_t*)target.nativeFilePath().utf16(), true) != 0; + if(!ret) + error = QSystemError(::GetLastError(), QSystemError::NativeError); + return ret; +} + +//static +bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +{ + bool ret = ::MoveFile((wchar_t*)source.nativeFilePath().utf16(), + (wchar_t*)target.nativeFilePath().utf16()) != 0; + if(!ret) + error = QSystemError(::GetLastError(), QSystemError::NativeError); + return ret; +} + +//static +bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error) +{ + bool ret = ::DeleteFile((wchar_t*)entry.nativeFilePath().utf16()) != 0; + if(!ret) + error = QSystemError(::GetLastError(), QSystemError::NativeError); + return ret; +} + +//static +bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, + QFileSystemMetaData *data) +{ + Q_UNUSED(data); + int mode = 0; + + if (permissions & QFile::ReadOwner || permissions & QFile::ReadUser + || permissions & QFile::ReadGroup || permissions & QFile::ReadOther) + mode |= _S_IREAD; + if (permissions & QFile::WriteOwner || permissions & QFile::WriteUser + || permissions & QFile::WriteGroup || permissions & QFile::WriteOther) + mode |= _S_IWRITE; + + if (mode == 0) // not supported + return false; + + bool ret = (::_wchmod((wchar_t*)entry.nativeFilePath().utf16(), mode) == 0); + if(!ret) + error = QSystemError(errno, QSystemError::StandardLibraryError); + return ret; +} + +static inline QDateTime fileTimeToQDateTime(const FILETIME *time) +{ + QDateTime ret; + +#if defined(Q_OS_WINCE) + SYSTEMTIME systime; + FILETIME ftime; + systime.wYear = 1970; + systime.wMonth = 1; + systime.wDay = 1; + systime.wHour = 0; + systime.wMinute = 0; + systime.wSecond = 0; + systime.wMilliseconds = 0; + systime.wDayOfWeek = 4; + SystemTimeToFileTime(&systime, &ftime); + unsigned __int64 acttime = (unsigned __int64)time->dwHighDateTime << 32 | time->dwLowDateTime; + FileTimeToSystemTime(time, &systime); + unsigned __int64 time1970 = (unsigned __int64)ftime.dwHighDateTime << 32 | ftime.dwLowDateTime; + unsigned __int64 difftime = acttime - time1970; + difftime /= 10000000; + ret.setTime_t((unsigned int)difftime); +#else + SYSTEMTIME sTime, lTime; + FileTimeToSystemTime(time, &sTime); + SystemTimeToTzSpecificLocalTime(0, &sTime, &lTime); + ret.setDate(QDate(lTime.wYear, lTime.wMonth, lTime.wDay)); + ret.setTime(QTime(lTime.wHour, lTime.wMinute, lTime.wSecond, lTime.wMilliseconds)); +#endif + + return ret; +} + +QDateTime QFileSystemMetaData::creationTime() const +{ + return fileTimeToQDateTime(&creationTime_); +} +QDateTime QFileSystemMetaData::modificationTime() const +{ + return fileTimeToQDateTime(&lastWriteTime_); +} +QDateTime QFileSystemMetaData::accessTime() const +{ + return fileTimeToQDateTime(&lastAccessTime_); +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystementry.cpp b/src/corelib/io/qfilesystementry.cpp new file mode 100644 index 0000000000..ccbb10d82c --- /dev/null +++ b/src/corelib/io/qfilesystementry.cpp @@ -0,0 +1,383 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystementry_p.h" + +#include <QtCore/qdir.h> +#include <QtCore/qfile.h> +#include <QtCore/private/qfsfileengine_p.h> +#ifdef Q_OS_WIN +#include <QtCore/qstringbuilder.h> +#endif + +QT_BEGIN_NAMESPACE + +#ifdef Q_OS_WIN +static bool isUncRoot(const QString &server) +{ + QString localPath = QDir::toNativeSeparators(server); + if (!localPath.startsWith(QLatin1String("\\\\"))) + return false; + + int idx = localPath.indexOf(QLatin1Char('\\'), 2); + if (idx == -1 || idx + 1 == localPath.length()) + return true; + + localPath = localPath.right(localPath.length() - idx - 1).trimmed(); + return localPath.isEmpty(); +} + +static inline QString fixIfRelativeUncPath(const QString &path) +{ + QString currentPath = QDir::currentPath(); + if (currentPath.startsWith(QLatin1String("//"))) + return currentPath % QChar(QLatin1Char('/')) % path; + return path; +} +#endif + +QFileSystemEntry::QFileSystemEntry() + : m_lastSeparator(0), + m_firstDotInFileName(0), + m_lastDotInFileName(0) +{ +} + +/*! + \internal + Use this constructor when the path is supplied by user code, as it may contain a mix + of '/' and the native separator. + */ +QFileSystemEntry::QFileSystemEntry(const QString &filePath) + : m_filePath(QDir::fromNativeSeparators(filePath)), + m_lastSeparator(-2), + m_firstDotInFileName(-2), + m_lastDotInFileName(0) +{ +} + +/*! + \internal + Use this constructor when the path is guaranteed to be in internal format, i.e. all + directory separators are '/' and not the native separator. + */ +QFileSystemEntry::QFileSystemEntry(const QString &filePath, FromInternalPath /* dummy */) + : m_filePath(filePath), + m_lastSeparator(-2), + m_firstDotInFileName(-2), + m_lastDotInFileName(0) +{ +} + +/*! + \internal + Use this constructor when the path comes from a native API + */ +QFileSystemEntry::QFileSystemEntry(const NativePath &nativeFilePath, FromNativePath /* dummy */) + : m_nativeFilePath(nativeFilePath), + m_lastSeparator(-2), + m_firstDotInFileName(-2), + m_lastDotInFileName(0) +{ +} + +QFileSystemEntry::QFileSystemEntry(const QString &filePath, const NativePath &nativeFilePath) + : m_filePath(QDir::fromNativeSeparators(filePath)), + m_nativeFilePath(nativeFilePath), + m_lastSeparator(-2), + m_firstDotInFileName(-2), + m_lastDotInFileName(0) +{ +} + +QString QFileSystemEntry::filePath() const +{ + resolveFilePath(); + return m_filePath; +} + +QFileSystemEntry::NativePath QFileSystemEntry::nativeFilePath() const +{ + resolveNativeFilePath(); + return m_nativeFilePath; +} + +void QFileSystemEntry::resolveFilePath() const +{ + if (m_filePath.isEmpty() && !m_nativeFilePath.isEmpty()) { +#if defined(QFILESYSTEMENTRY_NATIVE_PATH_IS_UTF16) + m_filePath = QDir::fromNativeSeparators(m_nativeFilePath); +#ifdef Q_OS_WIN + if (m_filePath.startsWith(QLatin1String("//?/UNC/"))) + m_filePath = m_filePath.remove(2,6); + if (m_filePath.startsWith(QLatin1String("//?/"))) + m_filePath = m_filePath.remove(0,4); +#endif +#else + m_filePath = QDir::fromNativeSeparators(QFile::decodeName(m_nativeFilePath)); +#endif + } +} + +void QFileSystemEntry::resolveNativeFilePath() const +{ + if (!m_filePath.isEmpty() && m_nativeFilePath.isEmpty()) { +#ifdef Q_OS_WIN + QString filePath = m_filePath; + if (isRelative()) + filePath = fixIfRelativeUncPath(m_filePath); + m_nativeFilePath = QFSFileEnginePrivate::longFileName(QDir::toNativeSeparators(filePath)); +#elif defined(QFILESYSTEMENTRY_NATIVE_PATH_IS_UTF16) + m_nativeFilePath = QDir::toNativeSeparators(m_filePath); +#else + m_nativeFilePath = QFile::encodeName(QDir::toNativeSeparators(m_filePath)); +#endif + } +} + +QString QFileSystemEntry::fileName() const +{ + findLastSeparator(); +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (m_lastSeparator == -1 && m_filePath.length() >= 2 && m_filePath.at(1) == QLatin1Char(':')) + return m_filePath.mid(2); +#endif + return m_filePath.mid(m_lastSeparator + 1); +} + +QString QFileSystemEntry::path() const +{ + findLastSeparator(); + if (m_lastSeparator == -1) { +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (m_filePath.length() >= 2 && m_filePath.at(1) == QLatin1Char(':')) + return m_filePath.left(2); +#endif + return QString(QLatin1Char('.')); + } + if (m_lastSeparator == 0) + return QString(QLatin1Char('/')); +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (m_lastSeparator == 2 && m_filePath.at(1) == QLatin1Char(':')) + return m_filePath.left(m_lastSeparator + 1); +#endif + return m_filePath.left(m_lastSeparator); +} + +QString QFileSystemEntry::baseName() const +{ + findFileNameSeparators(); +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (m_lastSeparator == -1 && m_filePath.length() >= 2 && m_filePath.at(1) == QLatin1Char(':')) + return m_filePath.mid(2); +#endif + int length = -1; + if (m_firstDotInFileName >= 0) { + length = m_firstDotInFileName; + if (m_lastSeparator != -1) // avoid off by one + length--; + } + return m_filePath.mid(m_lastSeparator + 1, length); +} + +QString QFileSystemEntry::completeBaseName() const +{ + findFileNameSeparators(); +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (m_lastSeparator == -1 && m_filePath.length() >= 2 && m_filePath.at(1) == QLatin1Char(':')) + return m_filePath.mid(2); +#endif + int length = -1; + if (m_firstDotInFileName >= 0) { + length = m_firstDotInFileName + m_lastDotInFileName; + if (m_lastSeparator != -1) // avoid off by one + length--; + } + return m_filePath.mid(m_lastSeparator + 1, length); +} + +QString QFileSystemEntry::suffix() const +{ + findFileNameSeparators(); + + if (m_lastDotInFileName == -1) + return QString(); + + return m_filePath.mid(qMax((qint16)0, m_lastSeparator) + m_firstDotInFileName + m_lastDotInFileName + 1); +} + +QString QFileSystemEntry::completeSuffix() const +{ + findFileNameSeparators(); + if (m_firstDotInFileName == -1) + return QString(); + + return m_filePath.mid(qMax((qint16)0, m_lastSeparator) + m_firstDotInFileName + 1); +} + +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) +bool QFileSystemEntry::isRelative() const +{ + resolveFilePath(); + return (m_filePath.isEmpty() || (!m_filePath.isEmpty() && (m_filePath[0].unicode() != '/') + && (!(m_filePath.length() >= 2 && m_filePath[1].unicode() == ':')))); +} + +bool QFileSystemEntry::isAbsolute() const +{ + resolveFilePath(); + return (!m_filePath.isEmpty() && ((m_filePath.length() >= 3 + && (m_filePath[0].isLetter() && m_filePath[1].unicode() == ':' && m_filePath[2].unicode() == '/')) +#ifdef Q_OS_WIN + || (m_filePath.length() >= 2 && (m_filePath.at(0) == QLatin1Char('/') && m_filePath.at(1) == QLatin1Char('/'))) +#endif + )); +} +#else +bool QFileSystemEntry::isRelative() const +{ + return !isAbsolute(); +} + +bool QFileSystemEntry::isAbsolute() const +{ + resolveFilePath(); + return (!m_filePath.isEmpty() && (m_filePath[0].unicode() == '/')); +} +#endif + +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) +bool QFileSystemEntry::isDriveRoot() const +{ + resolveFilePath(); + return (m_filePath.length() == 3 + && m_filePath.at(0).isLetter() && m_filePath.at(1) == QLatin1Char(':') + && m_filePath.at(2) == QLatin1Char('/')); +} +#endif + +bool QFileSystemEntry::isRoot() const +{ + resolveFilePath(); + if (m_filePath == QLatin1String("/") +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + || isDriveRoot() +#if defined(Q_OS_WIN) + || isUncRoot(m_filePath) +#endif +#endif + ) + return true; + + return false; +} + +bool QFileSystemEntry::isEmpty() const +{ + resolveNativeFilePath(); + return m_nativeFilePath.isEmpty(); +} + +// private methods + +void QFileSystemEntry::findLastSeparator() const +{ + if (m_lastSeparator == -2) { + resolveFilePath(); + m_lastSeparator = -1; + for (int i = m_filePath.size() - 1; i >= 0; --i) { + if (m_filePath[i].unicode() == '/') { + m_lastSeparator = i; + break; + } + } + } +} + +void QFileSystemEntry::findFileNameSeparators() const +{ + if (m_firstDotInFileName == -2) { + resolveFilePath(); + int firstDotInFileName = -1; + int lastDotInFileName = -1; + int lastSeparator = m_lastSeparator; + + int stop; + if (lastSeparator < 0) { + lastSeparator = -1; + stop = 0; + } else { + stop = lastSeparator; + } + + int i = m_filePath.size() - 1; + for (; i >= stop; --i) { + if (m_filePath[i].unicode() == '.') { + firstDotInFileName = lastDotInFileName = i; + break; + } else if (m_filePath[i].unicode() == '/') { + lastSeparator = i; + break; + } + } + + if (lastSeparator != i) { + for (--i; i >= stop; --i) { + if (m_filePath[i].unicode() == '.') + firstDotInFileName = i; + else if (m_filePath[i].unicode() == '/') { + lastSeparator = i; + break; + } + } + } + m_lastSeparator = lastSeparator; + m_firstDotInFileName = firstDotInFileName == -1 ? -1 : firstDotInFileName - qMax(0, lastSeparator); + if (lastDotInFileName == -1) + m_lastDotInFileName = -1; + else if (firstDotInFileName == lastDotInFileName) + m_lastDotInFileName = 0; + else + m_lastDotInFileName = lastDotInFileName - firstDotInFileName; + } +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystementry_p.h b/src/corelib/io/qfilesystementry_p.h new file mode 100644 index 0000000000..d4d16d0de6 --- /dev/null +++ b/src/corelib/io/qfilesystementry_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMENTRY_P_H_INCLUDED +#define QFILESYSTEMENTRY_P_H_INCLUDED + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qstring.h> +#include <QtCore/qbytearray.h> + +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) +#define QFILESYSTEMENTRY_NATIVE_PATH_IS_UTF16 +#endif + +QT_BEGIN_NAMESPACE + +class QFileSystemEntry +{ +public: + +#ifndef QFILESYSTEMENTRY_NATIVE_PATH_IS_UTF16 + typedef QByteArray NativePath; +#else + typedef QString NativePath; +#endif + struct FromNativePath{}; + struct FromInternalPath{}; + + QFileSystemEntry(); + explicit QFileSystemEntry(const QString &filePath); + + QFileSystemEntry(const QString &filePath, FromInternalPath dummy); + QFileSystemEntry(const NativePath &nativeFilePath, FromNativePath dummy); + QFileSystemEntry(const QString &filePath, const NativePath &nativeFilePath); + + QString filePath() const; + QString fileName() const; + QString path() const; + NativePath nativeFilePath() const; + QString baseName() const; + QString completeBaseName() const; + QString suffix() const; + QString completeSuffix() const; + bool isAbsolute() const; + bool isRelative() const; + +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + bool isDriveRoot() const; +#endif + bool isRoot() const; + + bool isEmpty() const; + void clear() + { + *this = QFileSystemEntry(); + } + +private: + // creates the QString version out of the bytearray version + void resolveFilePath() const; + // creates the bytearray version out of the QString version + void resolveNativeFilePath() const; + // resolves the separator + void findLastSeparator() const; + // resolves the dots and the separator + void findFileNameSeparators() const; + + mutable QString m_filePath; // always has slashes as separator + mutable NativePath m_nativeFilePath; // native encoding and separators + + mutable qint16 m_lastSeparator; // index in m_filePath of last separator + mutable qint16 m_firstDotInFileName; // index after m_filePath for first dot (.) + mutable qint16 m_lastDotInFileName; // index after m_firstDotInFileName for last dot (.) +}; + +QT_END_NAMESPACE + +#endif // include guard diff --git a/src/corelib/io/qfilesystemiterator_p.h b/src/corelib/io/qfilesystemiterator_p.h new file mode 100644 index 0000000000..fb8bfe69e9 --- /dev/null +++ b/src/corelib/io/qfilesystemiterator_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMITERATOR_P_H_INCLUDED +#define QFILESYSTEMITERATOR_P_H_INCLUDED + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> + +#ifndef QT_NO_FILESYSTEMITERATOR + +#include <QtCore/qdir.h> +#include <QtCore/qdiriterator.h> +#include <QtCore/qstringlist.h> + +#include <QtCore/private/qfilesystementry_p.h> +#include <QtCore/private/qfilesystemmetadata_p.h> + +// Platform-specific headers +#if defined(Q_OS_WIN) +#elif defined (Q_OS_SYMBIAN) +#include <f32file.h> +#else +#include <QtCore/qscopedpointer.h> +#endif + +QT_BEGIN_NAMESPACE + +class QFileSystemIterator +{ +public: + QFileSystemIterator(const QFileSystemEntry &entry, QDir::Filters filters, + const QStringList &nameFilters, QDirIterator::IteratorFlags flags + = QDirIterator::FollowSymlinks | QDirIterator::Subdirectories); + ~QFileSystemIterator(); + + bool advance(QFileSystemEntry &fileEntry, QFileSystemMetaData &metaData); + +private: + QFileSystemEntry::NativePath nativePath; + + // Platform-specific data +#if defined(Q_OS_WIN) + QFileSystemEntry::NativePath dirPath; + HANDLE findFileHandle; + QStringList uncShares; + bool uncFallback; + int uncShareIndex; + bool onlyDirs; +#elif defined (Q_OS_SYMBIAN) + RDir dirHandle; + TEntryArray entries; + TInt lastError; + TInt entryIndex; +#else + QT_DIR *dir; + QT_DIRENT *dirEntry; +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_CYGWIN) + // for readdir_r + QScopedPointer<QT_DIRENT, QScopedPointerPodDeleter> mt_file; +#endif + int lastError; +#endif + + Q_DISABLE_COPY(QFileSystemIterator) +}; + +QT_END_NAMESPACE + +#endif // QT_NO_FILESYSTEMITERATOR + +#endif // include guard diff --git a/src/corelib/io/qfilesystemiterator_symbian.cpp b/src/corelib/io/qfilesystemiterator_symbian.cpp new file mode 100644 index 0000000000..a39f9c3096 --- /dev/null +++ b/src/corelib/io/qfilesystemiterator_symbian.cpp @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemiterator_p.h" +#include "qfilesystemengine_p.h" +#include <QtCore/private/qcore_symbian_p.h> + +QT_BEGIN_NAMESPACE + +QFileSystemIterator::QFileSystemIterator(const QFileSystemEntry &path, QDir::Filters filters, + const QStringList &nameFilters, QDirIterator::IteratorFlags iteratorFlags) + : lastError(KErrNone), entryIndex(-1) +{ + RFs& fs = qt_s60GetRFs(); + + nativePath = path.nativeFilePath(); + if (!nativePath.endsWith(QLatin1Char('\\'))) + nativePath.append(QLatin1Char('\\')); + + QString absPath = QFileSystemEngine::absoluteName(path).nativeFilePath(); + + if (!absPath.endsWith(QLatin1Char('\\'))) + absPath.append(QLatin1Char('\\')); + + int pathLen = absPath.length(); + if (pathLen > KMaxFileName) { + lastError = KErrBadName; + return; + } + + //set up server side filtering to reduce IPCs + //RDir won't accept all valid name filters e.g. "*. bar" + if (nameFilters.count() == 1 && !(filters & QDir::AllDirs) && iteratorFlags + == QDirIterator::NoIteratorFlags && pathLen + nameFilters[0].length() + <= KMaxFileName) { + //server side supports one mask - skip this for recursive mode or if only files should be filtered + absPath.append(nameFilters[0]); + } + + TUint symbianMask = 0; + if ((filters & QDir::Dirs) || (filters & QDir::AllDirs) || (iteratorFlags + & QDirIterator::Subdirectories)) + symbianMask |= KEntryAttDir; //include directories + if (filters & QDir::Hidden) + symbianMask |= KEntryAttHidden; + if (filters & QDir::System) + symbianMask |= KEntryAttSystem; + if (((filters & QDir::Files) == 0) && symbianMask == KEntryAttDir) + symbianMask |= KEntryAttMatchExclusive; //exclude non-directories + else if (symbianMask == 0) { + if ((filters & QDir::PermissionMask) == QDir::Writable) + symbianMask = KEntryAttMatchExclude | KEntryAttReadOnly; + } + + lastError = dirHandle.Open(fs, qt_QString2TPtrC(absPath), symbianMask); +} + +QFileSystemIterator::~QFileSystemIterator() +{ + dirHandle.Close(); +} + +bool QFileSystemIterator::advance(QFileSystemEntry &fileEntry, QFileSystemMetaData &metaData) +{ + //1st time, lastError is result of dirHandle.Open(), entries.Count() is 0 and entryIndex is -1 so initial read is triggered + //subsequent times, read is triggered each time we reach the end of the entry list + //final time, lastError is KErrEof so we don't need to read anymore. + ++entryIndex; + if (lastError == KErrNone && entryIndex >= entries.Count()) { + lastError = dirHandle.Read(entries); + entryIndex = 0; + } + + //each call to advance() gets the next entry from the entry list. + //from the final (or only) read call, KErrEof is returned together with a full buffer so we still need to go through the list + if ((lastError == KErrNone || lastError == KErrEof) && entryIndex < entries.Count()) { + Q_ASSERT(entryIndex >= 0); + const TEntry &entry(entries[entryIndex]); + fileEntry = QFileSystemEntry(nativePath + qt_TDesC2QString(entry.iName), QFileSystemEntry::FromNativePath()); + metaData.fillFromTEntry(entry); + return true; + } + + //TODO: error reporting, to allow user to distinguish empty directory from error condition. + + return false; +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemiterator_unix.cpp b/src/corelib/io/qfilesystemiterator_unix.cpp new file mode 100644 index 0000000000..3d6012b47c --- /dev/null +++ b/src/corelib/io/qfilesystemiterator_unix.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qfilesystemiterator_p.h" + +#ifndef QT_NO_FILESYSTEMITERATOR + +#include <stdlib.h> +#include <errno.h> + +QT_BEGIN_NAMESPACE + +QFileSystemIterator::QFileSystemIterator(const QFileSystemEntry &entry, QDir::Filters filters, + const QStringList &nameFilters, QDirIterator::IteratorFlags flags) + : nativePath(entry.nativeFilePath()) + , dir(0) + , dirEntry(0) + , lastError(0) +{ + Q_UNUSED(filters) + Q_UNUSED(nameFilters) + Q_UNUSED(flags) + + if ((dir = QT_OPENDIR(nativePath.constData())) == 0) { + lastError = errno; + } else { + + if (!nativePath.endsWith('/')) + nativePath.append('/'); + +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_CYGWIN) + // ### Race condition; we should use fpathconf and dirfd(). + size_t maxPathName = ::pathconf(nativePath.constData(), _PC_NAME_MAX); + if (maxPathName == size_t(-1)) + maxPathName = FILENAME_MAX; + maxPathName += sizeof(QT_DIRENT) + 1; + + QT_DIRENT *p = reinterpret_cast<QT_DIRENT*>(::malloc(maxPathName)); + Q_CHECK_PTR(p); + + mt_file.reset(p); +#endif + } +} + +QFileSystemIterator::~QFileSystemIterator() +{ + if (dir) + QT_CLOSEDIR(dir); +} + +bool QFileSystemIterator::advance(QFileSystemEntry &fileEntry, QFileSystemMetaData &metaData) +{ + if (!dir) + return false; + +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_CYGWIN) + lastError = QT_READDIR_R(dir, mt_file.data(), &dirEntry); + if (lastError) + return false; +#else + // ### add local lock to prevent breaking reentrancy + dirEntry = QT_READDIR(dir); +#endif // _POSIX_THREAD_SAFE_FUNCTIONS + + if (dirEntry) { + fileEntry = QFileSystemEntry(nativePath + QByteArray(dirEntry->d_name), QFileSystemEntry::FromNativePath()); + metaData.fillFromDirEnt(*dirEntry); + return true; + } + + lastError = errno; + return false; +} + +QT_END_NAMESPACE + +#endif // QT_NO_FILESYSTEMITERATOR diff --git a/src/corelib/io/qfilesystemiterator_win.cpp b/src/corelib/io/qfilesystemiterator_win.cpp new file mode 100644 index 0000000000..0e94130049 --- /dev/null +++ b/src/corelib/io/qfilesystemiterator_win.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#if _WIN32_WINNT < 0x0500 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif + +#include "qfilesystemiterator_p.h" +#include "qfilesystemengine_p.h" +#include "qplatformdefs.h" + +#include <QtCore/qt_windows.h> + +QT_BEGIN_NAMESPACE + +bool done = true; + +QFileSystemIterator::QFileSystemIterator(const QFileSystemEntry &entry, QDir::Filters filters, + const QStringList &nameFilters, QDirIterator::IteratorFlags flags) + : nativePath(entry.nativeFilePath()) + , dirPath(entry.filePath()) + , findFileHandle(INVALID_HANDLE_VALUE) + , uncFallback(false) + , uncShareIndex(0) + , onlyDirs(false) +{ + Q_UNUSED(nameFilters) + Q_UNUSED(flags) + if (nativePath.endsWith(QLatin1String(".lnk"))) { + QFileSystemMetaData metaData; + QFileSystemEntry link = QFileSystemEngine::getLinkTarget(entry, metaData); + nativePath = link.nativeFilePath(); + } + if (!nativePath.endsWith(QLatin1Char('\\'))) + nativePath.append(QLatin1Char('\\')); + nativePath.append(QLatin1Char('*')); + if (!dirPath.endsWith(QLatin1Char('/'))) + dirPath.append(QLatin1Char('/')); + if ((filters & (QDir::Dirs|QDir::Drives)) && (!(filters & (QDir::Files)))) + onlyDirs = true; +} + +QFileSystemIterator::~QFileSystemIterator() +{ + if (findFileHandle != INVALID_HANDLE_VALUE) + FindClose(findFileHandle); +} + +bool QFileSystemIterator::advance(QFileSystemEntry &fileEntry, QFileSystemMetaData &metaData) +{ + bool haveData = false; + WIN32_FIND_DATA findData; + + if (findFileHandle == INVALID_HANDLE_VALUE && !uncFallback) { + haveData = true; + int infoLevel = 0 ; // FindExInfoStandard; + DWORD dwAdditionalFlags = 0; + if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS7) { + dwAdditionalFlags = 2; // FIND_FIRST_EX_LARGE_FETCH + infoLevel = 1 ; // FindExInfoBasic; + } + int searchOps = 0; // FindExSearchNameMatch + if (onlyDirs) + searchOps = 1 ; // FindExSearchLimitToDirectories + findFileHandle = FindFirstFileEx((const wchar_t *)nativePath.utf16(), FINDEX_INFO_LEVELS(infoLevel), &findData, + FINDEX_SEARCH_OPS(searchOps), 0, dwAdditionalFlags); + if (findFileHandle == INVALID_HANDLE_VALUE) { + if (nativePath.startsWith(QLatin1String("\\\\?\\UNC\\"))) { + QStringList parts = nativePath.split(QLatin1Char('\\'), QString::SkipEmptyParts); + if (parts.count() == 4 && QFileSystemEngine::uncListSharesOnServer( + QLatin1String("\\\\") + parts.at(2), &uncShares)) { + if (uncShares.isEmpty()) + return false; // No shares found in the server + else + uncFallback = true; + } + } + } + } + if (findFileHandle == INVALID_HANDLE_VALUE && !uncFallback) + return false; + // Retrieve the new file information. + if (!haveData) { + if (uncFallback) { + if (++uncShareIndex >= uncShares.count()) + return false; + } else { + if (!FindNextFile(findFileHandle, &findData)) + return false; + } + } + // Create the new file system entry & meta data. + if (uncFallback) { + fileEntry = QFileSystemEntry(dirPath + uncShares.at(uncShareIndex)); + metaData.fillFromFileAttribute(FILE_ATTRIBUTE_DIRECTORY); + return true; + } else { + QString fileName = QString::fromWCharArray(findData.cFileName); + fileEntry = QFileSystemEntry(dirPath + fileName); + metaData = QFileSystemMetaData(); + if (!fileName.endsWith(QLatin1String(".lnk"))) { + metaData.fillFromFindData(findData, true); + } + return true; + } + return false; +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemmetadata_p.h b/src/corelib/io/qfilesystemmetadata_p.h new file mode 100644 index 0000000000..f7f1fa1b8d --- /dev/null +++ b/src/corelib/io/qfilesystemmetadata_p.h @@ -0,0 +1,400 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMMETADATA_P_H_INCLUDED +#define QFILESYSTEMMETADATA_P_H_INCLUDED + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qplatformdefs.h" +#include <QtCore/qglobal.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qabstractfileengine.h> + +// Platform-specific includes +#if defined(Q_OS_WIN) +#ifndef IO_REPARSE_TAG_SYMLINK +#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) +#endif +#elif defined(Q_OS_SYMBIAN) +#include <f32file.h> +#include <QtCore/private/qdatetime_p.h> +#endif + +QT_BEGIN_NAMESPACE + +class QFileSystemEngine; + +class QFileSystemMetaData +{ +public: + QFileSystemMetaData() + : knownFlagsMask(0) + { + } + + enum MetaDataFlag { + // Permissions, overlaps with QFile::Permissions + OtherReadPermission = 0x00000004, OtherWritePermission = 0x00000002, OtherExecutePermission = 0x00000001, + GroupReadPermission = 0x00000040, GroupWritePermission = 0x00000020, GroupExecutePermission = 0x00000010, + UserReadPermission = 0x00000400, UserWritePermission = 0x00000200, UserExecutePermission = 0x00000100, + OwnerReadPermission = 0x00004000, OwnerWritePermission = 0x00002000, OwnerExecutePermission = 0x00001000, + + OtherPermissions = OtherReadPermission | OtherWritePermission | OtherExecutePermission, + GroupPermissions = GroupReadPermission | GroupWritePermission | GroupExecutePermission, + UserPermissions = UserReadPermission | UserWritePermission | UserExecutePermission, + OwnerPermissions = OwnerReadPermission | OwnerWritePermission | OwnerExecutePermission, + + ReadPermissions = OtherReadPermission | GroupReadPermission | UserReadPermission | OwnerReadPermission, + WritePermissions = OtherWritePermission | GroupWritePermission | UserWritePermission | OwnerWritePermission, + ExecutePermissions = OtherExecutePermission | GroupExecutePermission | UserExecutePermission | OwnerExecutePermission, + + Permissions = OtherPermissions | GroupPermissions | UserPermissions | OwnerPermissions, + + // Type +#ifdef Q_OS_SYMBIAN + LinkType = 0, +#else + LinkType = 0x00010000, +#endif + FileType = 0x00020000, + DirectoryType = 0x00040000, +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) + BundleType = 0x00080000, + AliasType = 0x08000000, +#else + BundleType = 0x0, + AliasType = 0x0, +#endif +#if defined(Q_OS_WIN) + WinLnkType = 0x08000000, // Note: Uses the same position for AliasType on Mac +#else + WinLnkType = 0x0, +#endif + SequentialType = 0x00800000, // Note: overlaps with QAbstractFileEngine::RootFlag + + LegacyLinkType = LinkType | AliasType | WinLnkType, + + Type = LinkType | FileType | DirectoryType | BundleType | SequentialType | AliasType, + + // Attributes + HiddenAttribute = 0x00100000, + SizeAttribute = 0x00200000, // Note: overlaps with QAbstractFileEngine::LocalDiskFlag + ExistsAttribute = 0x00400000, + + Attributes = HiddenAttribute | SizeAttribute | ExistsAttribute, + + // Times + CreationTime = 0x01000000, // Note: overlaps with QAbstractFileEngine::Refresh + ModificationTime = 0x02000000, + AccessTime = 0x04000000, + + Times = CreationTime | ModificationTime | AccessTime, + + // Owner IDs + UserId = 0x10000000, + GroupId = 0x20000000, + + OwnerIds = UserId | GroupId, + + PosixStatFlags = QFileSystemMetaData::OtherPermissions + | QFileSystemMetaData::GroupPermissions + | QFileSystemMetaData::OwnerPermissions + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::SequentialType + | QFileSystemMetaData::SizeAttribute + | QFileSystemMetaData::Times + | QFileSystemMetaData::OwnerIds, + + SymbianTEntryFlags = QFileSystemMetaData::Permissions + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::SequentialType + | QFileSystemMetaData::Attributes + | QFileSystemMetaData::Times, +#if defined(Q_OS_WIN) + WinStatFlags = QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::HiddenAttribute + | QFileSystemMetaData::ExistsAttribute + | QFileSystemMetaData::SizeAttribute + | QFileSystemMetaData::Times, +#endif + + AllMetaDataFlags = 0xFFFFFFFF + + }; + Q_DECLARE_FLAGS(MetaDataFlags, MetaDataFlag) + + bool hasFlags(MetaDataFlags flags) const + { + return ((knownFlagsMask & flags) == flags); + } + + MetaDataFlags missingFlags(MetaDataFlags flags) + { + return flags & ~knownFlagsMask; + } + + void clear() + { + knownFlagsMask = 0; + } + + void clearFlags(MetaDataFlags flags = AllMetaDataFlags) + { + knownFlagsMask &= ~flags; + } + + bool exists() const { return (entryFlags & ExistsAttribute); } + + bool isLink() const { return (entryFlags & LinkType); } + bool isFile() const { return (entryFlags & FileType); } + bool isDirectory() const { return (entryFlags & DirectoryType); } + bool isBundle() const; + bool isAlias() const; + bool isLegacyLink() const { return (entryFlags & LegacyLinkType); } + bool isSequential() const { return (entryFlags & SequentialType); } + bool isHidden() const { return (entryFlags & HiddenAttribute); } +#if defined(Q_OS_WIN) + bool isLnkFile() const { return (entryFlags & WinLnkType); } +#else + bool isLnkFile() const { return false; } +#endif + + qint64 size() const { return size_; } + + QFile::Permissions permissions() const { return QFile::Permissions(Permissions & entryFlags); } + + QDateTime creationTime() const; + QDateTime modificationTime() const; + QDateTime accessTime() const; + + QDateTime fileTime(QAbstractFileEngine::FileTime time) const; + uint userId() const; + uint groupId() const; + uint ownerId(QAbstractFileEngine::FileOwner owner) const; + +#ifdef Q_OS_UNIX + void fillFromStatBuf(const QT_STATBUF &statBuffer); + void fillFromDirEnt(const QT_DIRENT &statBuffer); +#endif +#ifdef Q_OS_SYMBIAN + void fillFromTEntry(const TEntry& entry); + void fillFromVolumeInfo(const TVolumeInfo& info); +#endif + +#if defined(Q_OS_WIN) + inline void fillFromFileAttribute(DWORD fileAttribute, bool isDriveRoot = false); + inline void fillFromFindData(WIN32_FIND_DATA &findData, bool setLinkType = false, bool isDriveRoot = false); + inline void fillFromFindInfo(BY_HANDLE_FILE_INFORMATION &fileInfo); +#endif +private: + friend class QFileSystemEngine; + + MetaDataFlags knownFlagsMask; + MetaDataFlags entryFlags; + + qint64 size_; + + // Platform-specific data goes here: +#if defined(Q_OS_WIN) + DWORD fileAttribute_; + FILETIME creationTime_; + FILETIME lastAccessTime_; + FILETIME lastWriteTime_; +#elif defined(Q_OS_SYMBIAN) + TTime modificationTime_; +#else + time_t creationTime_; + time_t modificationTime_; + time_t accessTime_; + + uint userId_; + uint groupId_; +#endif + +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QFileSystemMetaData::MetaDataFlags) + +#if !defined(QWS) && !defined(Q_WS_QPA) && defined(Q_OS_MAC) +inline bool QFileSystemMetaData::isBundle() const { return (entryFlags & BundleType); } +inline bool QFileSystemMetaData::isAlias() const { return (entryFlags & AliasType); } +#else +inline bool QFileSystemMetaData::isBundle() const { return false; } +inline bool QFileSystemMetaData::isAlias() const { return false; } +#endif + +#if (defined(Q_OS_UNIX) && !defined (Q_OS_SYMBIAN)) || defined (Q_OS_WIN) +inline QDateTime QFileSystemMetaData::fileTime(QAbstractFileEngine::FileTime time) const +{ + switch (time) { + case QAbstractFileEngine::ModificationTime: + return modificationTime(); + + case QAbstractFileEngine::AccessTime: + return accessTime(); + + case QAbstractFileEngine::CreationTime: + return creationTime(); + } + + return QDateTime(); +} +#endif + +#if defined(Q_OS_UNIX) && !defined (Q_OS_SYMBIAN) +inline QDateTime QFileSystemMetaData::creationTime() const { return QDateTime::fromTime_t(creationTime_); } +inline QDateTime QFileSystemMetaData::modificationTime() const { return QDateTime::fromTime_t(modificationTime_); } +inline QDateTime QFileSystemMetaData::accessTime() const { return QDateTime::fromTime_t(accessTime_); } + +inline uint QFileSystemMetaData::userId() const { return userId_; } +inline uint QFileSystemMetaData::groupId() const { return groupId_; } + +inline uint QFileSystemMetaData::ownerId(QAbstractFileEngine::FileOwner owner) const +{ + if (owner == QAbstractFileEngine::OwnerUser) + return userId(); + else + return groupId(); +} +#endif + +#ifdef Q_OS_SYMBIAN +inline QDateTime QFileSystemMetaData::creationTime() const { return modificationTime(); } +inline QDateTime QFileSystemMetaData::modificationTime() const { return qt_symbian_TTime_To_QDateTime(modificationTime_); } +inline QDateTime QFileSystemMetaData::accessTime() const { return modificationTime(); } + +inline QDateTime QFileSystemMetaData::fileTime(QAbstractFileEngine::FileTime time) const +{ + Q_UNUSED(time); + return modificationTime(); +} +inline uint QFileSystemMetaData::userId() const { return (uint) -2; } +inline uint QFileSystemMetaData::groupId() const { return (uint) -2; } +inline uint QFileSystemMetaData::ownerId(QAbstractFileEngine::FileOwner owner) const +{ + Q_UNUSED(owner); + return (uint) -2; +} +#endif + +#if defined(Q_OS_WIN) +inline uint QFileSystemMetaData::userId() const { return (uint) -2; } +inline uint QFileSystemMetaData::groupId() const { return (uint) -2; } +inline uint QFileSystemMetaData::ownerId(QAbstractFileEngine::FileOwner owner) const +{ + if (owner == QAbstractFileEngine::OwnerUser) + return userId(); + else + return groupId(); +} + +inline void QFileSystemMetaData::fillFromFileAttribute(DWORD fileAttribute,bool isDriveRoot) +{ + fileAttribute_ = fileAttribute; + // Ignore the hidden attribute for drives. + if (!isDriveRoot && (fileAttribute_ & FILE_ATTRIBUTE_HIDDEN)) + entryFlags |= HiddenAttribute; + entryFlags |= ((fileAttribute & FILE_ATTRIBUTE_DIRECTORY) ? DirectoryType: FileType); + entryFlags |= ExistsAttribute; + knownFlagsMask |= FileType | DirectoryType | HiddenAttribute | ExistsAttribute; +} + +inline void QFileSystemMetaData::fillFromFindData(WIN32_FIND_DATA &findData, bool setLinkType, bool isDriveRoot) +{ + fillFromFileAttribute(findData.dwFileAttributes, isDriveRoot); + creationTime_ = findData.ftCreationTime; + lastAccessTime_ = findData.ftLastAccessTime; + lastWriteTime_ = findData.ftLastWriteTime; + if (fileAttribute_ & FILE_ATTRIBUTE_DIRECTORY) { + size_ = 0; + } else { + size_ = findData.nFileSizeHigh; + size_ <<= 32; + size_ += findData.nFileSizeLow; + } + knownFlagsMask |= Times | SizeAttribute; + if (setLinkType) { + knownFlagsMask |= LinkType; + entryFlags &= ~LinkType; +#if !defined(Q_OS_WINCE) + if ((fileAttribute_ & FILE_ATTRIBUTE_REPARSE_POINT) + && (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK + || findData.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT)) { + entryFlags |= LinkType; + } +#endif + + } +} + +inline void QFileSystemMetaData::fillFromFindInfo(BY_HANDLE_FILE_INFORMATION &fileInfo) +{ + fillFromFileAttribute(fileInfo.dwFileAttributes); + creationTime_ = fileInfo.ftCreationTime; + lastAccessTime_ = fileInfo.ftLastAccessTime; + lastWriteTime_ = fileInfo.ftLastWriteTime; + if (fileAttribute_ & FILE_ATTRIBUTE_DIRECTORY) { + size_ = 0; + } else { + size_ = fileInfo.nFileSizeHigh; + size_ <<= 32; + size_ += fileInfo.nFileSizeLow; + } + knownFlagsMask |= Times | SizeAttribute; +} +#endif + +QT_END_NAMESPACE + +#endif // include guard diff --git a/src/corelib/io/qfilesystemwatcher.cpp b/src/corelib/io/qfilesystemwatcher.cpp new file mode 100644 index 0000000000..e0f2f44913 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher.cpp @@ -0,0 +1,640 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemwatcher.h" +#include "qfilesystemwatcher_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <qdatetime.h> +#include <qdebug.h> +#include <qdir.h> +#include <qfileinfo.h> +#include <qmutex.h> +#include <qset.h> +#include <qtimer.h> + +#if defined(Q_OS_WIN) +# include "qfilesystemwatcher_win_p.h" +#elif defined(Q_OS_LINUX) +# include "qfilesystemwatcher_inotify_p.h" +# include "qfilesystemwatcher_dnotify_p.h" +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_MAC) +# if (defined Q_OS_MAC) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +# include "qfilesystemwatcher_fsevents_p.h" +# endif //MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +# include "qfilesystemwatcher_kqueue_p.h" +#elif defined(Q_OS_SYMBIAN) +# include "qfilesystemwatcher_symbian_p.h" +#endif + +QT_BEGIN_NAMESPACE + +enum { PollingInterval = 1000 }; + +class QPollingFileSystemWatcherEngine : public QFileSystemWatcherEngine +{ + Q_OBJECT + + class FileInfo + { + uint ownerId; + uint groupId; + QFile::Permissions permissions; + QDateTime lastModified; + QStringList entries; + + public: + FileInfo(const QFileInfo &fileInfo) + : ownerId(fileInfo.ownerId()), + groupId(fileInfo.groupId()), + permissions(fileInfo.permissions()), + lastModified(fileInfo.lastModified()) + { + if (fileInfo.isDir()) { + entries = fileInfo.absoluteDir().entryList(QDir::AllEntries); + } + } + FileInfo &operator=(const QFileInfo &fileInfo) + { + *this = FileInfo(fileInfo); + return *this; + } + + bool operator!=(const QFileInfo &fileInfo) const + { + if (fileInfo.isDir() && entries != fileInfo.absoluteDir().entryList(QDir::AllEntries)) + return true; + return (ownerId != fileInfo.ownerId() + || groupId != fileInfo.groupId() + || permissions != fileInfo.permissions() + || lastModified != fileInfo.lastModified()); + } + }; + + mutable QMutex mutex; + QHash<QString, FileInfo> files, directories; + +public: + QPollingFileSystemWatcherEngine(); + + void run(); + + QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories); + QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories); + + void stop(); + +private Q_SLOTS: + void timeout(); +}; + +QPollingFileSystemWatcherEngine::QPollingFileSystemWatcherEngine() +{ +#ifndef QT_NO_THREAD + moveToThread(this); +#endif +} + +void QPollingFileSystemWatcherEngine::run() +{ + QTimer timer; + connect(&timer, SIGNAL(timeout()), SLOT(timeout())); + timer.start(PollingInterval); + (void) exec(); +} + +QStringList QPollingFileSystemWatcherEngine::addPaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ + QMutexLocker locker(&mutex); + QStringList p = paths; + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + QFileInfo fi(path); + if (!fi.exists()) + continue; + if (fi.isDir()) { + if (!directories->contains(path)) + directories->append(path); + if (!path.endsWith(QLatin1Char('/'))) + fi = QFileInfo(path + QLatin1Char('/')); + this->directories.insert(path, fi); + } else { + if (!files->contains(path)) + files->append(path); + this->files.insert(path, fi); + } + it.remove(); + } + start(); + return p; +} + +QStringList QPollingFileSystemWatcherEngine::removePaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ + QMutexLocker locker(&mutex); + QStringList p = paths; + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + if (this->directories.remove(path)) { + directories->removeAll(path); + it.remove(); + } else if (this->files.remove(path)) { + files->removeAll(path); + it.remove(); + } + } + if (this->files.isEmpty() && this->directories.isEmpty()) { + locker.unlock(); + stop(); + wait(); + } + return p; +} + +void QPollingFileSystemWatcherEngine::stop() +{ + quit(); +} + +void QPollingFileSystemWatcherEngine::timeout() +{ + QMutexLocker locker(&mutex); + QMutableHashIterator<QString, FileInfo> fit(files); + while (fit.hasNext()) { + QHash<QString, FileInfo>::iterator x = fit.next(); + QString path = x.key(); + QFileInfo fi(path); + if (!fi.exists()) { + fit.remove(); + emit fileChanged(path, true); + } else if (x.value() != fi) { + x.value() = fi; + emit fileChanged(path, false); + } + } + QMutableHashIterator<QString, FileInfo> dit(directories); + while (dit.hasNext()) { + QHash<QString, FileInfo>::iterator x = dit.next(); + QString path = x.key(); + QFileInfo fi(path); + if (!path.endsWith(QLatin1Char('/'))) + fi = QFileInfo(path + QLatin1Char('/')); + if (!fi.exists()) { + dit.remove(); + emit directoryChanged(path, true); + } else if (x.value() != fi) { + fi.refresh(); + if (!fi.exists()) { + dit.remove(); + emit directoryChanged(path, true); + } else { + x.value() = fi; + emit directoryChanged(path, false); + } + } + + } +} + + + + +QFileSystemWatcherEngine *QFileSystemWatcherPrivate::createNativeEngine() +{ +#if defined(Q_OS_WIN) + return new QWindowsFileSystemWatcherEngine; +#elif defined(Q_OS_LINUX) + QFileSystemWatcherEngine *eng = QInotifyFileSystemWatcherEngine::create(); + if(!eng) + eng = QDnotifyFileSystemWatcherEngine::create(); + return eng; +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_MAC) +# if 0 && defined(Q_OS_MAC) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) + return QFSEventsFileSystemWatcherEngine::create(); + else +# endif + return QKqueueFileSystemWatcherEngine::create(); +#elif defined(Q_OS_SYMBIAN) + return new QSymbianFileSystemWatcherEngine; +#else + return 0; +#endif +} + +QFileSystemWatcherPrivate::QFileSystemWatcherPrivate() + : native(0), poller(0), forced(0) +{ +} + +void QFileSystemWatcherPrivate::init() +{ + Q_Q(QFileSystemWatcher); + native = createNativeEngine(); + if (native) { + QObject::connect(native, + SIGNAL(fileChanged(QString,bool)), + q, + SLOT(_q_fileChanged(QString,bool))); + QObject::connect(native, + SIGNAL(directoryChanged(QString,bool)), + q, + SLOT(_q_directoryChanged(QString,bool))); + } +} + +void QFileSystemWatcherPrivate::initForcedEngine(const QString &forceName) +{ + if(forced) + return; + + Q_Q(QFileSystemWatcher); + +#if defined(Q_OS_LINUX) + if(forceName == QLatin1String("inotify")) { + forced = QInotifyFileSystemWatcherEngine::create(); + } else if(forceName == QLatin1String("dnotify")) { + forced = QDnotifyFileSystemWatcherEngine::create(); + } +#else + Q_UNUSED(forceName); +#endif + + if(forced) { + QObject::connect(forced, + SIGNAL(fileChanged(QString,bool)), + q, + SLOT(_q_fileChanged(QString,bool))); + QObject::connect(forced, + SIGNAL(directoryChanged(QString,bool)), + q, + SLOT(_q_directoryChanged(QString,bool))); + } +} + +void QFileSystemWatcherPrivate::initPollerEngine() +{ + if(poller) + return; + + Q_Q(QFileSystemWatcher); + poller = new QPollingFileSystemWatcherEngine; // that was a mouthful + QObject::connect(poller, + SIGNAL(fileChanged(QString,bool)), + q, + SLOT(_q_fileChanged(QString,bool))); + QObject::connect(poller, + SIGNAL(directoryChanged(QString,bool)), + q, + SLOT(_q_directoryChanged(QString,bool))); +} + +void QFileSystemWatcherPrivate::_q_fileChanged(const QString &path, bool removed) +{ + Q_Q(QFileSystemWatcher); + if (!files.contains(path)) { + // the path was removed after a change was detected, but before we delivered the signal + return; + } + if (removed) + files.removeAll(path); + emit q->fileChanged(path); +} + +void QFileSystemWatcherPrivate::_q_directoryChanged(const QString &path, bool removed) +{ + Q_Q(QFileSystemWatcher); + if (!directories.contains(path)) { + // perhaps the path was removed after a change was detected, but before we delivered the signal + return; + } + if (removed) + directories.removeAll(path); + emit q->directoryChanged(path); +} + + + +/*! + \class QFileSystemWatcher + \brief The QFileSystemWatcher class provides an interface for monitoring files and directories for modifications. + \ingroup io + \since 4.2 + \reentrant + + QFileSystemWatcher monitors the file system for changes to files + and directories by watching a list of specified paths. + + Call addPath() to watch a particular file or directory. Multiple + paths can be added using the addPaths() function. Existing paths can + be removed by using the removePath() and removePaths() functions. + + QFileSystemWatcher examines each path added to it. Files that have + been added to the QFileSystemWatcher can be accessed using the + files() function, and directories using the directories() function. + + The fileChanged() signal is emitted when a file has been modified, + renamed or removed from disk. Similarly, the directoryChanged() + signal is emitted when a directory or its contents is modified or + removed. Note that QFileSystemWatcher stops monitoring files once + they have been renamed or removed from disk, and directories once + they have been removed from disk. + + \note On systems running a Linux kernel without inotify support, + file systems that contain watched paths cannot be unmounted. + + \note Windows CE does not support directory monitoring by + default as this depends on the file system driver installed. + + \note The act of monitoring files and directories for + modifications consumes system resources. This implies there is a + limit to the number of files and directories your process can + monitor simultaneously. On Mac OS X 10.4 and all BSD variants, for + example, an open file descriptor is required for each monitored + file. Some system limits the number of open file descriptors to 256 + by default. This means that addPath() and addPaths() will fail if + your process tries to add more than 256 files or directories to + the file system monitor. Also note that your process may have + other file descriptors open in addition to the ones for files + being monitored, and these other open descriptors also count in + the total. Mac OS X 10.5 and up use a different backend and do not + suffer from this issue. + + + \sa QFile, QDir +*/ + + +/*! + Constructs a new file system watcher object with the given \a parent. +*/ +QFileSystemWatcher::QFileSystemWatcher(QObject *parent) + : QObject(*new QFileSystemWatcherPrivate, parent) +{ + d_func()->init(); +} + +/*! + Constructs a new file system watcher object with the given \a parent + which monitors the specified \a paths list. +*/ +QFileSystemWatcher::QFileSystemWatcher(const QStringList &paths, QObject *parent) + : QObject(*new QFileSystemWatcherPrivate, parent) +{ + d_func()->init(); + addPaths(paths); +} + +/*! + Destroys the file system watcher. +*/ +QFileSystemWatcher::~QFileSystemWatcher() +{ + Q_D(QFileSystemWatcher); + if (d->native) { + d->native->stop(); + d->native->wait(); + delete d->native; + d->native = 0; + } + if (d->poller) { + d->poller->stop(); + d->poller->wait(); + delete d->poller; + d->poller = 0; + } + if (d->forced) { + d->forced->stop(); + d->forced->wait(); + delete d->forced; + d->forced = 0; + } +} + +/*! + Adds \a path to the file system watcher if \a path exists. The + path is not added if it does not exist, or if it is already being + monitored by the file system watcher. + + If \a path specifies a directory, the directoryChanged() signal + will be emitted when \a path is modified or removed from disk; + otherwise the fileChanged() signal is emitted when \a path is + modified, renamed or removed. + + \note There is a system dependent limit to the number of files and + directories that can be monitored simultaneously. If this limit + has been reached, \a path will not be added to the file system + watcher, and a warning message will be printed to \e{stderr}. + + \sa addPaths(), removePath() +*/ +void QFileSystemWatcher::addPath(const QString &path) +{ + if (path.isEmpty()) { + qWarning("QFileSystemWatcher::addPath: path is empty"); + return; + } + addPaths(QStringList(path)); +} + +/*! + Adds each path in \a paths to the file system watcher. Paths are + not added if they not exist, or if they are already being + monitored by the file system watcher. + + If a path specifies a directory, the directoryChanged() signal + will be emitted when the path is modified or removed from disk; + otherwise the fileChanged() signal is emitted when the path is + modified, renamed, or removed. + + \note There is a system dependent limit to the number of files and + directories that can be monitored simultaneously. If this limit + has been reached, the excess \a paths will not be added to the + file system watcher, and a warning message will be printed to + \e{stderr} for each path that could not be added. + + \sa addPath(), removePaths() +*/ +void QFileSystemWatcher::addPaths(const QStringList &paths) +{ + Q_D(QFileSystemWatcher); + if (paths.isEmpty()) { + qWarning("QFileSystemWatcher::addPaths: list is empty"); + return; + } + + QStringList p = paths; + QFileSystemWatcherEngine *engine = 0; + + if(!objectName().startsWith(QLatin1String("_qt_autotest_force_engine_"))) { + // Normal runtime case - search intelligently for best engine + if(d->native) { + engine = d->native; + } else { + d_func()->initPollerEngine(); + engine = d->poller; + } + + } else { + // Autotest override case - use the explicitly selected engine only + QString forceName = objectName().mid(26); + if(forceName == QLatin1String("poller")) { + qDebug() << "QFileSystemWatcher: skipping native engine, using only polling engine"; + d_func()->initPollerEngine(); + engine = d->poller; + } else if(forceName == QLatin1String("native")) { + qDebug() << "QFileSystemWatcher: skipping polling engine, using only native engine"; + engine = d->native; + } else { + qDebug() << "QFileSystemWatcher: skipping polling and native engine, using only explicit" << forceName << "engine"; + d_func()->initForcedEngine(forceName); + engine = d->forced; + } + } + + if(engine) + p = engine->addPaths(p, &d->files, &d->directories); + + if (!p.isEmpty()) + qWarning("QFileSystemWatcher: failed to add paths: %s", + qPrintable(p.join(QLatin1String(", ")))); +} + +/*! + Removes the specified \a path from the file system watcher. + + \sa removePaths(), addPath() +*/ +void QFileSystemWatcher::removePath(const QString &path) +{ + if (path.isEmpty()) { + qWarning("QFileSystemWatcher::removePath: path is empty"); + return; + } + removePaths(QStringList(path)); +} + +/*! + Removes the specified \a paths from the file system watcher. + + \sa removePath(), addPaths() +*/ +void QFileSystemWatcher::removePaths(const QStringList &paths) +{ + if (paths.isEmpty()) { + qWarning("QFileSystemWatcher::removePaths: list is empty"); + return; + } + Q_D(QFileSystemWatcher); + QStringList p = paths; + if (d->native) + p = d->native->removePaths(p, &d->files, &d->directories); + if (d->poller) + p = d->poller->removePaths(p, &d->files, &d->directories); + if (d->forced) + p = d->forced->removePaths(p, &d->files, &d->directories); +} + +/*! + \fn void QFileSystemWatcher::fileChanged(const QString &path) + + This signal is emitted when the file at the specified \a path is + modified, renamed or removed from disk. + + \sa directoryChanged() +*/ + +/*! + \fn void QFileSystemWatcher::directoryChanged(const QString &path) + + This signal is emitted when the directory at a specified \a path, + is modified (e.g., when a file is added, modified or deleted) or + removed from disk. Note that if there are several changes during a + short period of time, some of the changes might not emit this + signal. However, the last change in the sequence of changes will + always generate this signal. + + \sa fileChanged() +*/ + +/*! + \fn QStringList QFileSystemWatcher::directories() const + + Returns a list of paths to directories that are being watched. + + \sa files() +*/ + +/*! + \fn QStringList QFileSystemWatcher::files() const + + Returns a list of paths to files that are being watched. + + \sa directories() +*/ + +QStringList QFileSystemWatcher::directories() const +{ + Q_D(const QFileSystemWatcher); + return d->directories; +} + +QStringList QFileSystemWatcher::files() const +{ + Q_D(const QFileSystemWatcher); + return d->files; +} + +QT_END_NAMESPACE + +#include "moc_qfilesystemwatcher.cpp" + +#include "qfilesystemwatcher.moc" + +#endif // QT_NO_FILESYSTEMWATCHER + diff --git a/src/corelib/io/qfilesystemwatcher.h b/src/corelib/io/qfilesystemwatcher.h new file mode 100644 index 0000000000..26b3dec90f --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMWATCHER_H +#define QFILESYSTEMWATCHER_H + +#include <QtCore/qobject.h> + +#ifndef QT_NO_FILESYSTEMWATCHER + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QFileSystemWatcherPrivate; + +class Q_CORE_EXPORT QFileSystemWatcher : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QFileSystemWatcher) + +public: + QFileSystemWatcher(QObject *parent = 0); + QFileSystemWatcher(const QStringList &paths, QObject *parent = 0); + ~QFileSystemWatcher(); + + void addPath(const QString &file); + void addPaths(const QStringList &files); + void removePath(const QString &file); + void removePaths(const QStringList &files); + + QStringList files() const; + QStringList directories() const; + +Q_SIGNALS: + void fileChanged(const QString &path); + void directoryChanged(const QString &path); + +private: + Q_PRIVATE_SLOT(d_func(), void _q_fileChanged(const QString &path, bool removed)) + Q_PRIVATE_SLOT(d_func(), void _q_directoryChanged(const QString &path, bool removed)) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_FILESYSTEMWATCHER +#endif // QFILESYSTEMWATCHER_H diff --git a/src/corelib/io/qfilesystemwatcher_dnotify.cpp b/src/corelib/io/qfilesystemwatcher_dnotify.cpp new file mode 100644 index 0000000000..2fcadf1488 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_dnotify.cpp @@ -0,0 +1,461 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qfilesystemwatcher.h" +#include "qfilesystemwatcher_dnotify_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <qsocketnotifier.h> +#include <qcoreapplication.h> +#include <qfileinfo.h> +#include <qtimer.h> +#include <qwaitcondition.h> +#include <qmutex.h> +#include <dirent.h> +#include <qdir.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> + +#include "private/qcore_unix_p.h" + +#ifdef QT_LINUXBASE + +/* LSB doesn't standardize these */ +#define F_NOTIFY 1026 +#define DN_ACCESS 0x00000001 +#define DN_MODIFY 0x00000002 +#define DN_CREATE 0x00000004 +#define DN_DELETE 0x00000008 +#define DN_RENAME 0x00000010 +#define DN_ATTRIB 0x00000020 +#define DN_MULTISHOT 0x80000000 + +#endif + +QT_BEGIN_NAMESPACE + +static int qfswd_fileChanged_pipe[2]; +static void (*qfswd_old_sigio_handler)(int) = 0; +static void (*qfswd_old_sigio_action)(int, siginfo_t *, void *) = 0; +static void qfswd_sigio_monitor(int signum, siginfo_t *i, void *v) +{ + qt_safe_write(qfswd_fileChanged_pipe[1], reinterpret_cast<char*>(&i->si_fd), sizeof(int)); + + if (qfswd_old_sigio_handler && qfswd_old_sigio_handler != SIG_IGN) + qfswd_old_sigio_handler(signum); + if (qfswd_old_sigio_action) + qfswd_old_sigio_action(signum, i, v); +} + +class QDnotifySignalThread : public QThread +{ +Q_OBJECT +public: + QDnotifySignalThread(); + virtual ~QDnotifySignalThread(); + + void startNotify(); + + virtual void run(); + +signals: + void fdChanged(int); + +protected: + virtual bool event(QEvent *); + +private slots: + void readFromDnotify(); + +private: + QMutex mutex; + QWaitCondition wait; + bool isExecing; +}; + +Q_GLOBAL_STATIC(QDnotifySignalThread, dnotifySignal) + +QDnotifySignalThread::QDnotifySignalThread() +: isExecing(false) +{ + moveToThread(this); + + qt_safe_pipe(qfswd_fileChanged_pipe, O_NONBLOCK); + + struct sigaction oldAction; + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_sigaction = qfswd_sigio_monitor; + action.sa_flags = SA_SIGINFO; + ::sigaction(SIGIO, &action, &oldAction); + if (!(oldAction.sa_flags & SA_SIGINFO)) + qfswd_old_sigio_handler = oldAction.sa_handler; + else + qfswd_old_sigio_action = oldAction.sa_sigaction; +} + +QDnotifySignalThread::~QDnotifySignalThread() +{ + if(isRunning()) { + quit(); + QThread::wait(); + } +} + +bool QDnotifySignalThread::event(QEvent *e) +{ + if(e->type() == QEvent::User) { + QMutexLocker locker(&mutex); + isExecing = true; + wait.wakeAll(); + return true; + } else { + return QThread::event(e); + } +} + +void QDnotifySignalThread::startNotify() +{ + // Note: All this fancy waiting for the thread to enter its event + // loop is to avoid nasty messages at app shutdown when the + // QDnotifySignalThread singleton is deleted + start(); + mutex.lock(); + while(!isExecing) + wait.wait(&mutex); + mutex.unlock(); +} + +void QDnotifySignalThread::run() +{ + QSocketNotifier sn(qfswd_fileChanged_pipe[0], QSocketNotifier::Read, this); + connect(&sn, SIGNAL(activated(int)), SLOT(readFromDnotify())); + + QCoreApplication::instance()->postEvent(this, new QEvent(QEvent::User)); + (void) exec(); +} + +void QDnotifySignalThread::readFromDnotify() +{ + int fd; + int readrv = qt_safe_read(qfswd_fileChanged_pipe[0], reinterpret_cast<char*>(&fd), sizeof(int)); + // Only expect EAGAIN or EINTR. Other errors are assumed to be impossible. + if(readrv != -1) { + Q_ASSERT(readrv == sizeof(int)); + Q_UNUSED(readrv); + + if(0 == fd) + quit(); + else + emit fdChanged(fd); + } +} + +QDnotifyFileSystemWatcherEngine::QDnotifyFileSystemWatcherEngine() +{ + QObject::connect(dnotifySignal(), SIGNAL(fdChanged(int)), + this, SLOT(refresh(int)), Qt::DirectConnection); +} + +QDnotifyFileSystemWatcherEngine::~QDnotifyFileSystemWatcherEngine() +{ + QMutexLocker locker(&mutex); + + for(QHash<int, Directory>::ConstIterator iter = fdToDirectory.constBegin(); + iter != fdToDirectory.constEnd(); + ++iter) { + qt_safe_close(iter->fd); + if(iter->parentFd) + qt_safe_close(iter->parentFd); + } +} + +QDnotifyFileSystemWatcherEngine *QDnotifyFileSystemWatcherEngine::create() +{ + return new QDnotifyFileSystemWatcherEngine(); +} + +void QDnotifyFileSystemWatcherEngine::run() +{ + qFatal("QDnotifyFileSystemWatcherEngine thread should not be run"); +} + +QStringList QDnotifyFileSystemWatcherEngine::addPaths(const QStringList &paths, QStringList *files, QStringList *directories) +{ + QMutexLocker locker(&mutex); + + QStringList p = paths; + QMutableListIterator<QString> it(p); + + while (it.hasNext()) { + QString path = it.next(); + + QFileInfo fi(path); + + if(!fi.exists()) { + continue; + } + + bool isDir = fi.isDir(); + + if (isDir && directories->contains(path)) { + continue; // Skip monitored directories + } else if(!isDir && files->contains(path)) { + continue; // Skip monitored files + } + + if(!isDir) + path = fi.canonicalPath(); + + // Locate the directory entry (creating if needed) + int fd = pathToFD[path]; + + if(fd == 0) { + + QT_DIR *d = QT_OPENDIR(path.toUtf8().constData()); + if(!d) continue; // Could not open directory + QT_DIR *parent = 0; + + QDir parentDir(path); + if(!parentDir.isRoot()) { + parentDir.cdUp(); + parent = QT_OPENDIR(parentDir.path().toUtf8().constData()); + if(!parent) { + QT_CLOSEDIR(d); + continue; + } + } + + fd = qt_safe_dup(::dirfd(d)); + int parentFd = parent ? qt_safe_dup(::dirfd(parent)) : 0; + + QT_CLOSEDIR(d); + if(parent) QT_CLOSEDIR(parent); + + Q_ASSERT(fd); + if(::fcntl(fd, F_SETSIG, SIGIO) || + ::fcntl(fd, F_NOTIFY, DN_MODIFY | DN_CREATE | DN_DELETE | + DN_RENAME | DN_ATTRIB | DN_MULTISHOT) || + (parent && ::fcntl(parentFd, F_SETSIG, SIGIO)) || + (parent && ::fcntl(parentFd, F_NOTIFY, DN_DELETE | DN_RENAME | + DN_MULTISHOT))) { + continue; // Could not set appropriate flags + } + + Directory dir; + dir.path = path; + dir.fd = fd; + dir.parentFd = parentFd; + + fdToDirectory.insert(fd, dir); + pathToFD.insert(path, fd); + if(parentFd) + parentToFD.insert(parentFd, fd); + } + + Directory &directory = fdToDirectory[fd]; + + if(isDir) { + directory.isMonitored = true; + } else { + Directory::File file; + file.path = fi.filePath(); + file.lastWrite = fi.lastModified(); + directory.files.append(file); + pathToFD.insert(fi.filePath(), fd); + } + + it.remove(); + + if(isDir) { + directories->append(path); + } else { + files->append(fi.filePath()); + } + } + + dnotifySignal()->startNotify(); + + return p; +} + +QStringList QDnotifyFileSystemWatcherEngine::removePaths(const QStringList &paths, QStringList *files, QStringList *directories) +{ + QMutexLocker locker(&mutex); + + QStringList p = paths; + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + + QString path = it.next(); + int fd = pathToFD.take(path); + + if(!fd) + continue; + + Directory &directory = fdToDirectory[fd]; + bool isDir = false; + if(directory.path == path) { + isDir = true; + directory.isMonitored = false; + } else { + for(int ii = 0; ii < directory.files.count(); ++ii) { + if(directory.files.at(ii).path == path) { + directory.files.removeAt(ii); + break; + } + } + } + + if(!directory.isMonitored && directory.files.isEmpty()) { + // No longer needed + qt_safe_close(directory.fd); + pathToFD.remove(directory.path); + fdToDirectory.remove(fd); + } + + if(isDir) { + directories->removeAll(path); + } else { + files->removeAll(path); + } + + it.remove(); + } + + return p; +} + +void QDnotifyFileSystemWatcherEngine::refresh(int fd) +{ + QMutexLocker locker(&mutex); + + bool wasParent = false; + QHash<int, Directory>::Iterator iter = fdToDirectory.find(fd); + if(iter == fdToDirectory.end()) { + QHash<int, int>::Iterator pIter = parentToFD.find(fd); + if(pIter == parentToFD.end()) + return; + + iter = fdToDirectory.find(*pIter); + if (iter == fdToDirectory.end()) + return; + wasParent = true; + } + + Directory &directory = *iter; + + if(!wasParent) { + for(int ii = 0; ii < directory.files.count(); ++ii) { + Directory::File &file = directory.files[ii]; + if(file.updateInfo()) { + // Emit signal + QString filePath = file.path; + bool removed = !QFileInfo(filePath).exists(); + + if(removed) { + directory.files.removeAt(ii); + --ii; + } + + emit fileChanged(filePath, removed); + } + } + } + + if(directory.isMonitored) { + // Emit signal + bool removed = !QFileInfo(directory.path).exists(); + QString path = directory.path; + + if(removed) + directory.isMonitored = false; + + emit directoryChanged(path, removed); + } + + if(!directory.isMonitored && directory.files.isEmpty()) { + qt_safe_close(directory.fd); + if(directory.parentFd) { + qt_safe_close(directory.parentFd); + parentToFD.remove(directory.parentFd); + } + fdToDirectory.erase(iter); + } +} + +void QDnotifyFileSystemWatcherEngine::stop() +{ +} + +bool QDnotifyFileSystemWatcherEngine::Directory::File::updateInfo() +{ + QFileInfo fi(path); + QDateTime nLastWrite = fi.lastModified(); + uint nOwnerId = fi.ownerId(); + uint nGroupId = fi.groupId(); + QFile::Permissions nPermissions = fi.permissions(); + + if(nLastWrite != lastWrite || + nOwnerId != ownerId || + nGroupId != groupId || + nPermissions != permissions) { + ownerId = nOwnerId; + groupId = nGroupId; + permissions = nPermissions; + lastWrite = nLastWrite; + return true; + } else { + return false; + } +} + +QT_END_NAMESPACE + +#include "qfilesystemwatcher_dnotify.moc" + +#endif // QT_NO_FILESYSTEMWATCHER diff --git a/src/corelib/io/qfilesystemwatcher_dnotify_p.h b/src/corelib/io/qfilesystemwatcher_dnotify_p.h new file mode 100644 index 0000000000..9531a3e81b --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_dnotify_p.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMWATCHER_DNOTIFY_P_H +#define QFILESYSTEMWATCHER_DNOTIFY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qfilesystemwatcher_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <qmutex.h> +#include <qhash.h> +#include <qdatetime.h> +#include <qfile.h> + +QT_BEGIN_NAMESPACE + +class QDnotifyFileSystemWatcherEngine : public QFileSystemWatcherEngine +{ + Q_OBJECT + +public: + virtual ~QDnotifyFileSystemWatcherEngine(); + + static QDnotifyFileSystemWatcherEngine *create(); + + void run(); + + QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories); + QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories); + + void stop(); + +private Q_SLOTS: + void refresh(int); + +private: + struct Directory { + Directory() : fd(0), parentFd(0), isMonitored(false) {} + Directory(const Directory &o) : path(o.path), + fd(o.fd), + parentFd(o.parentFd), + isMonitored(o.isMonitored), + files(o.files) {} + QString path; + int fd; + int parentFd; + bool isMonitored; + + struct File { + File() : ownerId(0u), groupId(0u), permissions(0u) { } + File(const File &o) : path(o.path), + ownerId(o.ownerId), + groupId(o.groupId), + permissions(o.permissions), + lastWrite(o.lastWrite) {} + QString path; + + bool updateInfo(); + + uint ownerId; + uint groupId; + QFile::Permissions permissions; + QDateTime lastWrite; + }; + + QList<File> files; + }; + + QDnotifyFileSystemWatcherEngine(); + + QMutex mutex; + QHash<QString, int> pathToFD; + QHash<int, Directory> fdToDirectory; + QHash<int, int> parentToFD; +}; + + + +QT_END_NAMESPACE +#endif // QT_NO_FILESYSTEMWATCHER +#endif // QFILESYSTEMWATCHER_DNOTIFY_P_H diff --git a/src/corelib/io/qfilesystemwatcher_fsevents.cpp b/src/corelib/io/qfilesystemwatcher_fsevents.cpp new file mode 100644 index 0000000000..19b720c8b3 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_fsevents.cpp @@ -0,0 +1,492 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <qplatformdefs.h> + +#include "qfilesystemwatcher.h" +#include "qfilesystemwatcher_fsevents_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <qdebug.h> +#include <qfile.h> +#include <qdatetime.h> +#include <qfileinfo.h> +#include <qvarlengtharray.h> + +#include <mach/mach.h> +#include <sys/types.h> +#include <CoreFoundation/CFRunLoop.h> +#include <CoreFoundation/CFUUID.h> +#include <CoreServices/CoreServices.h> +#include <AvailabilityMacros.h> +#include <private/qcore_mac_p.h> + +QT_BEGIN_NAMESPACE + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 +// Static operator overloading so for the sake of some convieniece. +// They only live in this compilation unit to avoid polluting Qt in general. +static bool operator==(const struct ::timespec &left, const struct ::timespec &right) +{ + return left.tv_sec == right.tv_sec + && left.tv_nsec == right.tv_nsec; +} + +static bool operator==(const struct ::stat64 &left, const struct ::stat64 &right) +{ + return left.st_dev == right.st_dev + && left.st_mode == right.st_mode + && left.st_size == right.st_size + && left.st_ino == right.st_ino + && left.st_uid == right.st_uid + && left.st_gid == right.st_gid + && left.st_mtimespec == right.st_mtimespec + && left.st_ctimespec == right.st_ctimespec + && left.st_flags == right.st_flags; +} + +static bool operator!=(const struct ::stat64 &left, const struct ::stat64 &right) +{ + return !(operator==(left, right)); +} + + +static void addPathToHash(PathHash &pathHash, const QString &key, const QFileInfo &fileInfo, + const QString &path) +{ + PathInfoList &list = pathHash[key]; + list.push_back(PathInfo(path, + fileInfo.canonicalFilePath().normalized(QString::NormalizationForm_D).toUtf8())); + pathHash.insert(key, list); +} + +static void removePathFromHash(PathHash &pathHash, const QString &key, const QString &path) +{ + PathInfoList &list = pathHash[key]; + // We make the assumption that the list contains unique paths + PathInfoList::iterator End = list.end(); + PathInfoList::iterator it = list.begin(); + while (it != End) { + if (it->originalPath == path) { + list.erase(it); + break; + } + ++it; + } + if (list.isEmpty()) + pathHash.remove(key); +} + +static void stopFSStream(FSEventStreamRef stream) +{ + if (stream) { + FSEventStreamStop(stream); + FSEventStreamInvalidate(stream); + } +} + +static QString createFSStreamPath(const QString &absolutePath) +{ + // The path returned has a trailing slash, so ensure that here. + QString string = absolutePath; + string.reserve(string.size() + 1); + string.append(QLatin1Char('/')); + return string; +} + +static void cleanupFSStream(FSEventStreamRef stream) +{ + if (stream) + FSEventStreamRelease(stream); +} + +const FSEventStreamCreateFlags QtFSEventFlags = (kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagNoDefer /* | kFSEventStreamCreateFlagWatchRoot*/); + +const CFTimeInterval Latency = 0.033; // This will do updates 30 times a second which is probably more than you need. +#endif + +QFSEventsFileSystemWatcherEngine::QFSEventsFileSystemWatcherEngine() + : fsStream(0), pathsToWatch(0), threadsRunLoop(0) +{ +} + +QFSEventsFileSystemWatcherEngine::~QFSEventsFileSystemWatcherEngine() +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + // I assume that at this point, QFileSystemWatcher has already called stop + // on me, so I don't need to invalidate or stop my stream, simply + // release it. + cleanupFSStream(fsStream); + if (pathsToWatch) + CFRelease(pathsToWatch); +#endif +} + +QFSEventsFileSystemWatcherEngine *QFSEventsFileSystemWatcherEngine::create() +{ + return new QFSEventsFileSystemWatcherEngine(); +} + +QStringList QFSEventsFileSystemWatcherEngine::addPaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + stop(); + wait(); + QMutexLocker locker(&mutex); + QStringList failedToAdd; + // if we have a running FSStreamEvent, we have to kill it, we'll re-add the stream soon. + FSEventStreamEventId idToCheck; + if (fsStream) { + idToCheck = FSEventStreamGetLatestEventId(fsStream); + cleanupFSStream(fsStream); + } else { + idToCheck = kFSEventStreamEventIdSinceNow; + } + + // Brain-dead approach, but works. FSEvents actually can already read sub-trees, but since it's + // work to figure out if we are doing a double register, we just register it twice as FSEvents + // seems smart enough to only deliver one event. We also duplicate directory entries in here + // (e.g., if you watch five files in the same directory, you get that directory included in the + // array 5 times). This stupidity also makes remove work correctly though. I'll freely admit + // that we could make this a bit smarter. If you do, check the auto-tests, they should catch at + // least a couple of the issues. + QCFType<CFMutableArrayRef> tmpArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + for (int i = 0; i < paths.size(); ++i) { + const QString &path = paths.at(i); + + QFileInfo fileInfo(path); + if (!fileInfo.exists()) { + failedToAdd.append(path); + continue; + } + + if (fileInfo.isDir()) { + if (directories->contains(path)) { + failedToAdd.append(path); + continue; + } else { + directories->append(path); + // Full file path for dirs. + QCFString cfpath(createFSStreamPath(fileInfo.canonicalFilePath())); + addPathToHash(dirPathInfoHash, cfpath, fileInfo, path); + CFArrayAppendValue(tmpArray, cfpath); + } + } else { + if (files->contains(path)) { + failedToAdd.append(path); + continue; + } else { + // Just the absolute path (minus it's filename) for files. + QCFString cfpath(createFSStreamPath(fileInfo.canonicalPath())); + files->append(path); + addPathToHash(filePathInfoHash, cfpath, fileInfo, path); + CFArrayAppendValue(tmpArray, cfpath); + } + } + } + + if (!pathsToWatch && failedToAdd.size() == paths.size()) { + return failedToAdd; + } + + if (CFArrayGetCount(tmpArray) > 0) { + if (pathsToWatch) { + CFArrayAppendArray(tmpArray, pathsToWatch, CFRangeMake(0, CFArrayGetCount(pathsToWatch))); + CFRelease(pathsToWatch); + } + pathsToWatch = CFArrayCreateCopy(kCFAllocatorDefault, tmpArray); + } + + FSEventStreamContext context = { 0, this, 0, 0, 0 }; + fsStream = FSEventStreamCreate(kCFAllocatorDefault, + QFSEventsFileSystemWatcherEngine::fseventsCallback, + &context, pathsToWatch, + idToCheck, Latency, QtFSEventFlags); + warmUpFSEvents(); + + return failedToAdd; +#else + Q_UNUSED(paths); + Q_UNUSED(files); + Q_UNUSED(directories); + return QStringList(); +#endif +} + +void QFSEventsFileSystemWatcherEngine::warmUpFSEvents() +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + // This function assumes that the mutex has already been grabbed before calling it. + // It exits with the mutex still locked (Q_ASSERT(mutex.isLocked()) ;-). + start(); + waitCondition.wait(&mutex); +#endif +} + +QStringList QFSEventsFileSystemWatcherEngine::removePaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + stop(); + wait(); + QMutexLocker locker(&mutex); + // short circuit for smarties that call remove before add and we have nothing. + if (pathsToWatch == 0) + return paths; + QStringList failedToRemove; + // if we have a running FSStreamEvent, we have to stop it, we'll re-add the stream soon. + FSEventStreamEventId idToCheck; + if (fsStream) { + idToCheck = FSEventStreamGetLatestEventId(fsStream); + cleanupFSStream(fsStream); + fsStream = 0; + } else { + idToCheck = kFSEventStreamEventIdSinceNow; + } + + CFIndex itemCount = CFArrayGetCount(pathsToWatch); + QCFType<CFMutableArrayRef> tmpArray = CFArrayCreateMutableCopy(kCFAllocatorDefault, itemCount, + pathsToWatch); + CFRelease(pathsToWatch); + pathsToWatch = 0; + for (int i = 0; i < paths.size(); ++i) { + // Get the itemCount at the beginning to avoid any overruns during the iteration. + itemCount = CFArrayGetCount(tmpArray); + const QString &path = paths.at(i); + QFileInfo fi(path); + QCFString cfpath(createFSStreamPath(fi.canonicalPath())); + + CFIndex index = CFArrayGetFirstIndexOfValue(tmpArray, CFRangeMake(0, itemCount), cfpath); + if (index != -1) { + CFArrayRemoveValueAtIndex(tmpArray, index); + files->removeAll(path); + removePathFromHash(filePathInfoHash, cfpath, path); + } else { + // Could be a directory we are watching instead. + QCFString cfdirpath(createFSStreamPath(fi.canonicalFilePath())); + index = CFArrayGetFirstIndexOfValue(tmpArray, CFRangeMake(0, itemCount), cfdirpath); + if (index != -1) { + CFArrayRemoveValueAtIndex(tmpArray, index); + directories->removeAll(path); + removePathFromHash(dirPathInfoHash, cfpath, path); + } else { + failedToRemove.append(path); + } + } + } + itemCount = CFArrayGetCount(tmpArray); + if (itemCount != 0) { + pathsToWatch = CFArrayCreateCopy(kCFAllocatorDefault, tmpArray); + + FSEventStreamContext context = { 0, this, 0, 0, 0 }; + fsStream = FSEventStreamCreate(kCFAllocatorDefault, + QFSEventsFileSystemWatcherEngine::fseventsCallback, + &context, pathsToWatch, idToCheck, Latency, QtFSEventFlags); + warmUpFSEvents(); + } + return failedToRemove; +#else + Q_UNUSED(paths); + Q_UNUSED(files); + Q_UNUSED(directories); + return QStringList(); +#endif +} + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 +void QFSEventsFileSystemWatcherEngine::updateList(PathInfoList &list, bool directory, bool emitSignals) +{ + PathInfoList::iterator End = list.end(); + PathInfoList::iterator it = list.begin(); + while (it != End) { + struct ::stat64 newInfo; + if (::stat64(it->absolutePath, &newInfo) == 0) { + if (emitSignals) { + if (newInfo != it->savedInfo) { + it->savedInfo = newInfo; + if (directory) + emit directoryChanged(it->originalPath, false); + else + emit fileChanged(it->originalPath, false); + } + } else { + it->savedInfo = newInfo; + } + } else { + if (errno == ENOENT) { + if (emitSignals) { + if (directory) + emit directoryChanged(it->originalPath, true); + else + emit fileChanged(it->originalPath, true); + } + it = list.erase(it); + continue; + } else { + qWarning("%s:%d:QFSEventsFileSystemWatcherEngine: stat error on %s:%s", + __FILE__, __LINE__, qPrintable(it->originalPath), strerror(errno)); + + } + } + ++it; + } +} + +void QFSEventsFileSystemWatcherEngine::updateHash(PathHash &pathHash) +{ + PathHash::iterator HashEnd = pathHash.end(); + PathHash::iterator it = pathHash.begin(); + const bool IsDirectory = (&pathHash == &dirPathInfoHash); + while (it != HashEnd) { + updateList(it.value(), IsDirectory, false); + if (it.value().isEmpty()) + it = pathHash.erase(it); + else + ++it; + } +} +#endif + +void QFSEventsFileSystemWatcherEngine::fseventsCallback(ConstFSEventStreamRef , + void *clientCallBackInfo, size_t numEvents, + void *eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId []) +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + QFSEventsFileSystemWatcherEngine *watcher = static_cast<QFSEventsFileSystemWatcherEngine *>(clientCallBackInfo); + QMutexLocker locker(&watcher->mutex); + CFArrayRef paths = static_cast<CFArrayRef>(eventPaths); + for (size_t i = 0; i < numEvents; ++i) { + const QString path = QCFString::toQString( + static_cast<CFStringRef>(CFArrayGetValueAtIndex(paths, i))); + const FSEventStreamEventFlags pathFlags = eventFlags[i]; + // There are several flags that may be passed, but we really don't care about them ATM. + // Here they are and why we don't care. + // kFSEventStreamEventFlagHistoryDone--(very unlikely to be gotten, but even then, not much changes). + // kFSEventStreamEventFlagMustScanSubDirs--Likely means the data is very much out of date, we + // aren't coalescing our directories, so again not so much of an issue + // kFSEventStreamEventFlagRootChanged | kFSEventStreamEventFlagMount | kFSEventStreamEventFlagUnmount-- + // These three flags indicate something has changed, but the stat will likely show this, so + // there's not really much to worry about. + // (btw, FSEvents is not the correct way of checking for mounts/unmounts, + // there are real CarbonCore events for that.) + Q_UNUSED(pathFlags); + if (watcher->filePathInfoHash.contains(path)) + watcher->updateList(watcher->filePathInfoHash[path], false, true); + + if (watcher->dirPathInfoHash.contains(path)) + watcher->updateList(watcher->dirPathInfoHash[path], true, true); + } +#else + Q_UNUSED(clientCallBackInfo); + Q_UNUSED(numEvents); + Q_UNUSED(eventPaths); + Q_UNUSED(eventFlags); +#endif +} + +void QFSEventsFileSystemWatcherEngine::stop() +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + QMutexLocker locker(&mutex); + stopFSStream(fsStream); + if (threadsRunLoop) { + CFRunLoopStop(threadsRunLoop); + waitForStop.wait(&mutex); + } +#endif +} + +void QFSEventsFileSystemWatcherEngine::updateFiles() +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + QMutexLocker locker(&mutex); + updateHash(filePathInfoHash); + updateHash(dirPathInfoHash); + if (filePathInfoHash.isEmpty() && dirPathInfoHash.isEmpty()) { + // Everything disappeared before we got to start, don't bother. +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + // Code duplicated from stop(), with the exception that we + // don't wait on waitForStop here. Doing this will lead to + // a deadlock since this function is called from the worker + // thread. (waitForStop.wakeAll() is only called from the + // end of run()). + stopFSStream(fsStream); + if (threadsRunLoop) + CFRunLoopStop(threadsRunLoop); +#endif + cleanupFSStream(fsStream); + } + waitCondition.wakeAll(); +#endif +} + +void QFSEventsFileSystemWatcherEngine::run() +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + threadsRunLoop = CFRunLoopGetCurrent(); + FSEventStreamScheduleWithRunLoop(fsStream, threadsRunLoop, kCFRunLoopDefaultMode); + bool startedOK = FSEventStreamStart(fsStream); + // It's recommended by Apple that you only update the files after you've started + // the stream, because otherwise you might miss an update in between starting it. + updateFiles(); +#ifdef QT_NO_DEBUG + Q_UNUSED(startedOK); +#else + Q_ASSERT(startedOK); +#endif + // If for some reason we called stop up above (and invalidated our stream), this call will return + // immediately. + CFRunLoopRun(); + threadsRunLoop = 0; + QMutexLocker locker(&mutex); + waitForStop.wakeAll(); +#endif +} + +QT_END_NAMESPACE +#endif //QT_NO_FILESYSTEMWATCHER diff --git a/src/corelib/io/qfilesystemwatcher_fsevents_p.h b/src/corelib/io/qfilesystemwatcher_fsevents_p.h new file mode 100644 index 0000000000..bbfdd32fd4 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_fsevents_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef FILEWATCHER_FSEVENTS_P_H +#define FILEWATCHER_FSEVENTS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qfilesystemwatcher_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <QtCore/qmutex.h> +#include <QtCore/qwaitcondition.h> +#include <QtCore/qthread.h> +#include <QtCore/qhash.h> +#include <QtCore/qlinkedlist.h> +#include <private/qcore_mac_p.h> +#include <sys/stat.h> + +typedef struct __FSEventStream *FSEventStreamRef; +typedef const struct __FSEventStream *ConstFSEventStreamRef; +typedef const struct __CFArray *CFArrayRef; +typedef UInt32 FSEventStreamEventFlags; +typedef uint64_t FSEventStreamEventId; + +QT_BEGIN_NAMESPACE + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 +// Yes, I use a stat64 element here. QFileInfo requires too much knowledge about implementation +// details to be used as a long-standing record. Since I'm going to have to store this information, I can +// do the stat myself too. +struct PathInfo { + PathInfo(const QString &path, const QByteArray &absPath) + : originalPath(path), absolutePath(absPath) {} + QString originalPath; // The path we need to emit + QByteArray absolutePath; // The path we need to stat. + struct ::stat64 savedInfo; // All the info for the path so we can compare it. +}; +typedef QLinkedList<PathInfo> PathInfoList; +typedef QHash<QString, PathInfoList> PathHash; +#endif + +class QFSEventsFileSystemWatcherEngine : public QFileSystemWatcherEngine +{ + Q_OBJECT +public: + ~QFSEventsFileSystemWatcherEngine(); + + static QFSEventsFileSystemWatcherEngine *create(); + + QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories); + QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories); + + void stop(); + +private: + QFSEventsFileSystemWatcherEngine(); + void warmUpFSEvents(); + void updateFiles(); + + static void fseventsCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, + void *eventPaths, const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]); + void run(); + FSEventStreamRef fsStream; + CFArrayRef pathsToWatch; + CFRunLoopRef threadsRunLoop; + QMutex mutex; + QWaitCondition waitCondition; + QWaitCondition waitForStop; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + PathHash filePathInfoHash; + PathHash dirPathInfoHash; + void updateHash(PathHash &pathHash); + void updateList(PathInfoList &list, bool directory, bool emitSignals); +#endif +}; + +#endif //QT_NO_FILESYSTEMWATCHER + +#endif + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemwatcher_inotify.cpp b/src/corelib/io/qfilesystemwatcher_inotify.cpp new file mode 100644 index 0000000000..8fc2d31312 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_inotify.cpp @@ -0,0 +1,410 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemwatcher.h" +#include "qfilesystemwatcher_inotify_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include "private/qcore_unix_p.h" + +#include <qdebug.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qsocketnotifier.h> +#include <qvarlengtharray.h> + +#include <sys/syscall.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <fcntl.h> + +#if defined(QT_NO_INOTIFY) +#include <linux/types.h> + +#if defined(__i386__) +# define __NR_inotify_init 291 +# define __NR_inotify_add_watch 292 +# define __NR_inotify_rm_watch 293 +# define __NR_inotify_init1 332 +#elif defined(__x86_64__) +# define __NR_inotify_init 253 +# define __NR_inotify_add_watch 254 +# define __NR_inotify_rm_watch 255 +# define __NR_inotify_init1 294 +#elif defined(__powerpc__) || defined(__powerpc64__) +# define __NR_inotify_init 275 +# define __NR_inotify_add_watch 276 +# define __NR_inotify_rm_watch 277 +# define __NR_inotify_init1 318 +#elif defined (__ia64__) +# define __NR_inotify_init 1277 +# define __NR_inotify_add_watch 1278 +# define __NR_inotify_rm_watch 1279 +# define __NR_inotify_init1 1318 +#elif defined (__s390__) || defined (__s390x__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +# define __NR_inotify_init1 324 +#elif defined (__alpha__) +# define __NR_inotify_init 444 +# define __NR_inotify_add_watch 445 +# define __NR_inotify_rm_watch 446 +// no inotify_init1 for the Alpha +#elif defined (__sparc__) || defined (__sparc64__) +# define __NR_inotify_init 151 +# define __NR_inotify_add_watch 152 +# define __NR_inotify_rm_watch 156 +# define __NR_inotify_init1 322 +#elif defined (__arm__) +# define __NR_inotify_init 316 +# define __NR_inotify_add_watch 317 +# define __NR_inotify_rm_watch 318 +# define __NR_inotify_init1 360 +#elif defined (__sh__) +# define __NR_inotify_init 290 +# define __NR_inotify_add_watch 291 +# define __NR_inotify_rm_watch 292 +# define __NR_inotify_init1 332 +#elif defined (__sh64__) +# define __NR_inotify_init 318 +# define __NR_inotify_add_watch 319 +# define __NR_inotify_rm_watch 320 +# define __NR_inotify_init1 360 +#elif defined (__mips__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +# define __NR_inotify_init1 329 +#elif defined (__hppa__) +# define __NR_inotify_init 269 +# define __NR_inotify_add_watch 270 +# define __NR_inotify_rm_watch 271 +# define __NR_inotify_init1 314 +#elif defined (__avr32__) +# define __NR_inotify_init 240 +# define __NR_inotify_add_watch 241 +# define __NR_inotify_rm_watch 242 +// no inotify_init1 for AVR32 +#elif defined (__mc68000__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +# define __NR_inotify_init1 328 +#else +# error "This architecture is not supported. Please talk to qt-bugs@trolltech.com" +#endif + +#if !defined(IN_CLOEXEC) && defined(O_CLOEXEC) && defined(__NR_inotify_init1) +# define IN_CLOEXEC O_CLOEXEC +#endif + +QT_BEGIN_NAMESPACE + +#ifdef QT_LINUXBASE +// ### the LSB doesn't standardize syscall, need to wait until glib2.4 is standardized +static inline int syscall(...) { return -1; } +#endif + +static inline int inotify_init() +{ + return syscall(__NR_inotify_init); +} + +static inline int inotify_add_watch(int fd, const char *name, __u32 mask) +{ + return syscall(__NR_inotify_add_watch, fd, name, mask); +} + +static inline int inotify_rm_watch(int fd, __u32 wd) +{ + return syscall(__NR_inotify_rm_watch, fd, wd); +} + +#ifdef IN_CLOEXEC +static inline int inotify_init1(int flags) +{ + return syscall(__NR_inotify_init1, flags); +} +#endif + +// the following struct and values are documented in linux/inotify.h +extern "C" { + +struct inotify_event { + __s32 wd; + __u32 mask; + __u32 cookie; + __u32 len; + char name[0]; +}; + +#define IN_ACCESS 0x00000001 +#define IN_MODIFY 0x00000002 +#define IN_ATTRIB 0x00000004 +#define IN_CLOSE_WRITE 0x00000008 +#define IN_CLOSE_NOWRITE 0x00000010 +#define IN_OPEN 0x00000020 +#define IN_MOVED_FROM 0x00000040 +#define IN_MOVED_TO 0x00000080 +#define IN_CREATE 0x00000100 +#define IN_DELETE 0x00000200 +#define IN_DELETE_SELF 0x00000400 +#define IN_MOVE_SELF 0x00000800 +#define IN_UNMOUNT 0x00002000 +#define IN_Q_OVERFLOW 0x00004000 +#define IN_IGNORED 0x00008000 + +#define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) +#define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) +} + +QT_END_NAMESPACE + +// --------- inotify.h end ---------- + +#else /* QT_NO_INOTIFY */ + +#include <sys/inotify.h> + +#endif + +QT_BEGIN_NAMESPACE + +QInotifyFileSystemWatcherEngine *QInotifyFileSystemWatcherEngine::create() +{ + register int fd = -1; +#ifdef IN_CLOEXEC + fd = inotify_init1(IN_CLOEXEC); +#endif + if (fd == -1) { + fd = inotify_init(); + if (fd == -1) + return 0; + ::fcntl(fd, F_SETFD, FD_CLOEXEC); + } + return new QInotifyFileSystemWatcherEngine(fd); +} + +QInotifyFileSystemWatcherEngine::QInotifyFileSystemWatcherEngine(int fd) + : inotifyFd(fd) +{ + fcntl(inotifyFd, F_SETFD, FD_CLOEXEC); + + moveToThread(this); +} + +QInotifyFileSystemWatcherEngine::~QInotifyFileSystemWatcherEngine() +{ + foreach (int id, pathToID) + inotify_rm_watch(inotifyFd, id < 0 ? -id : id); + + ::close(inotifyFd); +} + +void QInotifyFileSystemWatcherEngine::run() +{ + QSocketNotifier sn(inotifyFd, QSocketNotifier::Read, this); + connect(&sn, SIGNAL(activated(int)), SLOT(readFromInotify())); + (void) exec(); +} + +QStringList QInotifyFileSystemWatcherEngine::addPaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ + QMutexLocker locker(&mutex); + + QStringList p = paths; + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + QFileInfo fi(path); + bool isDir = fi.isDir(); + if (isDir) { + if (directories->contains(path)) + continue; + } else { + if (files->contains(path)) + continue; + } + + int wd = inotify_add_watch(inotifyFd, + QFile::encodeName(path), + (isDir + ? (0 + | IN_ATTRIB + | IN_MOVE + | IN_CREATE + | IN_DELETE + | IN_DELETE_SELF + ) + : (0 + | IN_ATTRIB + | IN_MODIFY + | IN_MOVE + | IN_MOVE_SELF + | IN_DELETE_SELF + ))); + if (wd <= 0) { + perror("QInotifyFileSystemWatcherEngine::addPaths: inotify_add_watch failed"); + continue; + } + + it.remove(); + + int id = isDir ? -wd : wd; + if (id < 0) { + directories->append(path); + } else { + files->append(path); + } + + pathToID.insert(path, id); + idToPath.insert(id, path); + } + + start(); + + return p; +} + +QStringList QInotifyFileSystemWatcherEngine::removePaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ + QMutexLocker locker(&mutex); + + QStringList p = paths; + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + int id = pathToID.take(path); + QString x = idToPath.take(id); + if (x.isEmpty() || x != path) + continue; + + int wd = id < 0 ? -id : id; + // qDebug() << "removing watch for path" << path << "wd" << wd; + inotify_rm_watch(inotifyFd, wd); + + it.remove(); + if (id < 0) { + directories->removeAll(path); + } else { + files->removeAll(path); + } + } + + return p; +} + +void QInotifyFileSystemWatcherEngine::stop() +{ + quit(); +} + +void QInotifyFileSystemWatcherEngine::readFromInotify() +{ + QMutexLocker locker(&mutex); + + // qDebug() << "QInotifyFileSystemWatcherEngine::readFromInotify"; + + int buffSize = 0; + ioctl(inotifyFd, FIONREAD, (char *) &buffSize); + QVarLengthArray<char, 4096> buffer(buffSize); + buffSize = read(inotifyFd, buffer.data(), buffSize); + char *at = buffer.data(); + char * const end = at + buffSize; + + QHash<int, inotify_event *> eventForId; + while (at < end) { + inotify_event *event = reinterpret_cast<inotify_event *>(at); + + if (eventForId.contains(event->wd)) + eventForId[event->wd]->mask |= event->mask; + else + eventForId.insert(event->wd, event); + + at += sizeof(inotify_event) + event->len; + } + + QHash<int, inotify_event *>::const_iterator it = eventForId.constBegin(); + while (it != eventForId.constEnd()) { + const inotify_event &event = **it; + ++it; + + // qDebug() << "inotify event, wd" << event.wd << "mask" << hex << event.mask; + + int id = event.wd; + QString path = idToPath.value(id); + if (path.isEmpty()) { + // perhaps a directory? + id = -id; + path = idToPath.value(id); + if (path.isEmpty()) + continue; + } + + // qDebug() << "event for path" << path; + + if ((event.mask & (IN_DELETE_SELF | IN_MOVE_SELF | IN_UNMOUNT)) != 0) { + pathToID.remove(path); + idToPath.remove(id); + inotify_rm_watch(inotifyFd, event.wd); + + if (id < 0) + emit directoryChanged(path, true); + else + emit fileChanged(path, true); + } else { + if (id < 0) + emit directoryChanged(path, false); + else + emit fileChanged(path, false); + } + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_FILESYSTEMWATCHER diff --git a/src/corelib/io/qfilesystemwatcher_inotify_p.h b/src/corelib/io/qfilesystemwatcher_inotify_p.h new file mode 100644 index 0000000000..870d9b48d5 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_inotify_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMWATCHER_INOTIFY_P_H +#define QFILESYSTEMWATCHER_INOTIFY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qfilesystemwatcher_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <qhash.h> +#include <qmutex.h> + +QT_BEGIN_NAMESPACE + +class QInotifyFileSystemWatcherEngine : public QFileSystemWatcherEngine +{ + Q_OBJECT + +public: + ~QInotifyFileSystemWatcherEngine(); + + static QInotifyFileSystemWatcherEngine *create(); + + void run(); + + QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories); + QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories); + + void stop(); + +private Q_SLOTS: + void readFromInotify(); + +private: + QInotifyFileSystemWatcherEngine(int fd); + int inotifyFd; + QMutex mutex; + QHash<QString, int> pathToID; + QHash<int, QString> idToPath; +}; + + +QT_END_NAMESPACE +#endif // QT_NO_FILESYSTEMWATCHER +#endif // QFILESYSTEMWATCHER_INOTIFY_P_H diff --git a/src/corelib/io/qfilesystemwatcher_kqueue.cpp b/src/corelib/io/qfilesystemwatcher_kqueue.cpp new file mode 100644 index 0000000000..6c36a82fd1 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_kqueue.cpp @@ -0,0 +1,334 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qplatformdefs.h> + +#include "qfilesystemwatcher.h" +#include "qfilesystemwatcher_kqueue_p.h" +#include "private/qcore_unix_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <qdebug.h> +#include <qfile.h> +#include <qsocketnotifier.h> +#include <qvarlengtharray.h> + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <fcntl.h> + +QT_BEGIN_NAMESPACE + +// #define KEVENT_DEBUG +#ifdef KEVENT_DEBUG +# define DEBUG qDebug +#else +# define DEBUG if(false)qDebug +#endif + +QKqueueFileSystemWatcherEngine *QKqueueFileSystemWatcherEngine::create() +{ + int kqfd = kqueue(); + if (kqfd == -1) + return 0; + return new QKqueueFileSystemWatcherEngine(kqfd); +} + +QKqueueFileSystemWatcherEngine::QKqueueFileSystemWatcherEngine(int kqfd) + : kqfd(kqfd) +{ + fcntl(kqfd, F_SETFD, FD_CLOEXEC); + + if (pipe(kqpipe) == -1) { + perror("QKqueueFileSystemWatcherEngine: cannot create pipe"); + kqpipe[0] = kqpipe[1] = -1; + return; + } + fcntl(kqpipe[0], F_SETFD, FD_CLOEXEC); + fcntl(kqpipe[1], F_SETFD, FD_CLOEXEC); + + struct kevent kev; + EV_SET(&kev, + kqpipe[0], + EVFILT_READ, + EV_ADD | EV_ENABLE, + 0, + 0, + 0); + if (kevent(kqfd, &kev, 1, 0, 0, 0) == -1) { + perror("QKqueueFileSystemWatcherEngine: cannot watch pipe, kevent returned"); + return; + } +} + +QKqueueFileSystemWatcherEngine::~QKqueueFileSystemWatcherEngine() +{ + stop(); + wait(); + + close(kqfd); + close(kqpipe[0]); + close(kqpipe[1]); + + foreach (int id, pathToID) + ::close(id < 0 ? -id : id); +} + +QStringList QKqueueFileSystemWatcherEngine::addPaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ + QStringList p = paths; + { + QMutexLocker locker(&mutex); + + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + int fd; +#if defined(O_EVTONLY) + fd = qt_safe_open(QFile::encodeName(path), O_EVTONLY); +#else + fd = qt_safe_open(QFile::encodeName(path), O_RDONLY); +#endif + if (fd == -1) { + perror("QKqueueFileSystemWatcherEngine::addPaths: open"); + continue; + } + if (fd >= (int)FD_SETSIZE / 2 && fd < (int)FD_SETSIZE) { + int fddup = fcntl(fd, F_DUPFD, FD_SETSIZE); + if (fddup != -1) { + ::close(fd); + fd = fddup; + } + } + fcntl(fd, F_SETFD, FD_CLOEXEC); + + QT_STATBUF st; + if (QT_FSTAT(fd, &st) == -1) { + perror("QKqueueFileSystemWatcherEngine::addPaths: fstat"); + ::close(fd); + continue; + } + int id = (S_ISDIR(st.st_mode)) ? -fd : fd; + if (id < 0) { + if (directories->contains(path)) { + ::close(fd); + continue; + } + } else { + if (files->contains(path)) { + ::close(fd); + continue; + } + } + + struct kevent kev; + EV_SET(&kev, + fd, + EVFILT_VNODE, + EV_ADD | EV_ENABLE | EV_CLEAR, + NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE, + 0, + 0); + if (kevent(kqfd, &kev, 1, 0, 0, 0) == -1) { + perror("QKqueueFileSystemWatcherEngine::addPaths: kevent"); + ::close(fd); + continue; + } + + it.remove(); + if (id < 0) { + DEBUG() << "QKqueueFileSystemWatcherEngine: added directory path" << path; + directories->append(path); + } else { + DEBUG() << "QKqueueFileSystemWatcherEngine: added file path" << path; + files->append(path); + } + + pathToID.insert(path, id); + idToPath.insert(id, path); + } + } + + if (!isRunning()) + start(); + else + write(kqpipe[1], "@", 1); + + return p; +} + +QStringList QKqueueFileSystemWatcherEngine::removePaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ + bool isEmpty; + QStringList p = paths; + { + QMutexLocker locker(&mutex); + if (pathToID.isEmpty()) + return p; + + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + int id = pathToID.take(path); + QString x = idToPath.take(id); + if (x.isEmpty() || x != path) + continue; + + ::close(id < 0 ? -id : id); + + it.remove(); + if (id < 0) + directories->removeAll(path); + else + files->removeAll(path); + } + isEmpty = pathToID.isEmpty(); + } + + if (isEmpty) { + stop(); + wait(); + } else { + write(kqpipe[1], "@", 1); + } + + return p; +} + +void QKqueueFileSystemWatcherEngine::stop() +{ + write(kqpipe[1], "q", 1); +} + +void QKqueueFileSystemWatcherEngine::run() +{ + forever { + int r; + struct kevent kev; + DEBUG() << "QKqueueFileSystemWatcherEngine: waiting for kevents..."; + EINTR_LOOP(r, kevent(kqfd, 0, 0, &kev, 1, 0)); + if (r < 0) { + perror("QKqueueFileSystemWatcherEngine: error during kevent wait"); + return; + } else { + int fd = kev.ident; + + DEBUG() << "QKqueueFileSystemWatcherEngine: processing kevent" << kev.ident << kev.filter; + if (fd == kqpipe[0]) { + // read all pending data from the pipe + QByteArray ba; + ba.resize(kev.data); + if (read(kqpipe[0], ba.data(), ba.size()) != ba.size()) { + perror("QKqueueFileSystemWatcherEngine: error reading from pipe"); + return; + } + // read the command from the buffer (but break and return on 'q') + char cmd = 0; + for (int i = 0; i < ba.size(); ++i) { + cmd = ba.constData()[i]; + if (cmd == 'q') + break; + } + // handle the command + switch (cmd) { + case 'q': + DEBUG() << "QKqueueFileSystemWatcherEngine: thread received 'q', exiting..."; + return; + case '@': + DEBUG() << "QKqueueFileSystemWatcherEngine: thread received '@', continuing..."; + break; + default: + DEBUG() << "QKqueueFileSystemWatcherEngine: thread received unknow message" << cmd; + break; + } + } else { + QMutexLocker locker(&mutex); + + int id = fd; + QString path = idToPath.value(id); + if (path.isEmpty()) { + // perhaps a directory? + id = -id; + path = idToPath.value(id); + if (path.isEmpty()) { + DEBUG() << "QKqueueFileSystemWatcherEngine: received a kevent for a file we're not watching"; + continue; + } + } + if (kev.filter != EVFILT_VNODE) { + DEBUG() << "QKqueueFileSystemWatcherEngine: received a kevent with the wrong filter"; + continue; + } + + if ((kev.fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) != 0) { + DEBUG() << path << "removed, removing watch also"; + + pathToID.remove(path); + idToPath.remove(id); + ::close(fd); + + if (id < 0) + emit directoryChanged(path, true); + else + emit fileChanged(path, true); + } else { + DEBUG() << path << "changed"; + + if (id < 0) + emit directoryChanged(path, false); + else + emit fileChanged(path, false); + } + } + } + } +} + +#endif //QT_NO_FILESYSTEMWATCHER + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemwatcher_kqueue_p.h b/src/corelib/io/qfilesystemwatcher_kqueue_p.h new file mode 100644 index 0000000000..38b893753e --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_kqueue_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef FILEWATCHER_KQUEUE_P_H +#define FILEWATCHER_KQUEUE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qfilesystemwatcher_p.h" + +#include <QtCore/qhash.h> +#include <QtCore/qmutex.h> +#include <QtCore/qthread.h> +#include <QtCore/qvector.h> + +#ifndef QT_NO_FILESYSTEMWATCHER +struct kevent; + +QT_BEGIN_NAMESPACE + +class QKqueueFileSystemWatcherEngine : public QFileSystemWatcherEngine +{ + Q_OBJECT +public: + ~QKqueueFileSystemWatcherEngine(); + + static QKqueueFileSystemWatcherEngine *create(); + + QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories); + QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories); + + void stop(); + +private: + QKqueueFileSystemWatcherEngine(int kqfd); + + void run(); + + int kqfd; + int kqpipe[2]; + + QMutex mutex; + QHash<QString, int> pathToID; + QHash<int, QString> idToPath; +}; + +QT_END_NAMESPACE + +#endif //QT_NO_FILESYSTEMWATCHER +#endif // FILEWATCHER_KQUEUE_P_H diff --git a/src/corelib/io/qfilesystemwatcher_p.h b/src/corelib/io/qfilesystemwatcher_p.h new file mode 100644 index 0000000000..6fbf7531fc --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_p.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMWATCHER_P_H +#define QFILESYSTEMWATCHER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qfilesystemwatcher.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <private/qobject_p.h> + +#include <QtCore/qstringlist.h> +#include <QtCore/qthread.h> + +QT_BEGIN_NAMESPACE + +class QFileSystemWatcherEngine : public QThread +{ + Q_OBJECT + +protected: + inline QFileSystemWatcherEngine(bool move = true) + { + if (move) + moveToThread(this); + } + +public: + // fills \a files and \a directories with the \a paths it could + // watch, and returns a list of paths this engine could not watch + virtual QStringList addPaths(const QStringList &paths, + QStringList *files, + QStringList *directories) = 0; + // removes \a paths from \a files and \a directories, and returns + // a list of paths this engine does not know about (either addPath + // failed or wasn't called) + virtual QStringList removePaths(const QStringList &paths, + QStringList *files, + QStringList *directories) = 0; + + virtual void stop() = 0; + +Q_SIGNALS: + void fileChanged(const QString &path, bool removed); + void directoryChanged(const QString &path, bool removed); +}; + +class QFileSystemWatcherPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QFileSystemWatcher) + + static QFileSystemWatcherEngine *createNativeEngine(); + +public: + QFileSystemWatcherPrivate(); + void init(); + void initPollerEngine(); + void initForcedEngine(const QString &); + + QFileSystemWatcherEngine *native, *poller, *forced; + QStringList files, directories; + + // private slots + void _q_fileChanged(const QString &path, bool removed); + void _q_directoryChanged(const QString &path, bool removed); +}; + + +QT_END_NAMESPACE +#endif // QT_NO_FILESYSTEMWATCHER +#endif // QFILESYSTEMWATCHER_P_H diff --git a/src/corelib/io/qfilesystemwatcher_symbian.cpp b/src/corelib/io/qfilesystemwatcher_symbian.cpp new file mode 100644 index 0000000000..8e8dfe5f30 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_symbian.cpp @@ -0,0 +1,273 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemwatcher.h" +#include "qfilesystemwatcher_symbian_p.h" +#include "qfileinfo.h" +#include "qdebug.h" +#include "private/qcore_symbian_p.h" +#include <QDir> + +#ifndef QT_NO_FILESYSTEMWATCHER + + +QT_BEGIN_NAMESPACE + +QNotifyChangeEvent::QNotifyChangeEvent(RFs &fs, const TDesC &file, + QSymbianFileSystemWatcherEngine *e, bool aIsDir, + TInt aPriority) : + CActive(aPriority), + isDir(aIsDir), + fsSession(fs), + watchedPath(file), + engine(e), + failureCount(0) +{ + if (isDir) { + fsSession.NotifyChange(ENotifyEntry, iStatus, file); + } else { + fsSession.NotifyChange(ENotifyAll, iStatus, file); + } + CActiveScheduler::Add(this); + SetActive(); +} + +QNotifyChangeEvent::~QNotifyChangeEvent() +{ + Cancel(); +} + +void QNotifyChangeEvent::RunL() +{ + if(iStatus.Int() == KErrNone) { + failureCount = 0; + } else { + qWarning("QNotifyChangeEvent::RunL() - Failed to order change notifications: %d", iStatus.Int()); + failureCount++; + } + + // Re-request failed notification once, but if it won't start working, + // we can't do much besides just not request any more notifications. + if (failureCount < 2) { + if (isDir) { + fsSession.NotifyChange(ENotifyEntry, iStatus, watchedPath); + } else { + fsSession.NotifyChange(ENotifyAll, iStatus, watchedPath); + } + SetActive(); + + if (!failureCount) { + int err; + QT_TRYCATCH_ERROR(err, engine->emitPathChanged(this)); + if (err != KErrNone) + qWarning("QNotifyChangeEvent::RunL() - emitPathChanged threw exception (Converted error code: %d)", err); + } + } +} + +void QNotifyChangeEvent::DoCancel() +{ + fsSession.NotifyChangeCancel(iStatus); +} + +QSymbianFileSystemWatcherEngine::QSymbianFileSystemWatcherEngine() : + watcherStarted(false) +{ + moveToThread(this); +} + +QSymbianFileSystemWatcherEngine::~QSymbianFileSystemWatcherEngine() +{ + stop(); +} + +QStringList QSymbianFileSystemWatcherEngine::addPaths(const QStringList &paths, QStringList *files, + QStringList *directories) +{ + QMutexLocker locker(&mutex); + QStringList p = paths; + + startWatcher(); + + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + QFileInfo fi(path); + if (!fi.exists()) + continue; + + bool isDir = fi.isDir(); + if (isDir) { + if (directories->contains(path)) + continue; + } else { + if (files->contains(path)) + continue; + } + + // Use absolute filepath as relative paths seem to have some issues. + QString filePath = fi.absoluteFilePath(); + if (isDir && filePath.at(filePath.size() - 1) != QChar(L'/')) { + filePath += QChar(L'/'); + } + + currentAddEvent = NULL; + QMetaObject::invokeMethod(this, + "addNativeListener", + Qt::QueuedConnection, + Q_ARG(QString, filePath)); + + syncCondition.wait(&mutex); + if (currentAddEvent) { + currentAddEvent->isDir = isDir; + + activeObjectToPath.insert(currentAddEvent, path); + it.remove(); + + if (isDir) + directories->append(path); + else + files->append(path); + } + } + + return p; +} + +QStringList QSymbianFileSystemWatcherEngine::removePaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ + QMutexLocker locker(&mutex); + + QStringList p = paths; + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + + currentRemoveEvent = activeObjectToPath.key(path); + if (!currentRemoveEvent) + continue; + activeObjectToPath.remove(currentRemoveEvent); + + QMetaObject::invokeMethod(this, + "removeNativeListener", + Qt::QueuedConnection); + + syncCondition.wait(&mutex); + + it.remove(); + + files->removeAll(path); + directories->removeAll(path); + } + + return p; +} + +void QSymbianFileSystemWatcherEngine::emitPathChanged(QNotifyChangeEvent *e) +{ + QMutexLocker locker(&mutex); + + QString path = activeObjectToPath.value(e); + QFileInfo fi(path); + + if (e->isDir) + emit directoryChanged(path, !fi.exists()); + else + emit fileChanged(path, !fi.exists()); +} + +void QSymbianFileSystemWatcherEngine::stop() +{ + quit(); + wait(); +} + +// This method must be called inside mutex +void QSymbianFileSystemWatcherEngine::startWatcher() +{ + if (!watcherStarted) { + setStackSize(0x5000); + start(); + syncCondition.wait(&mutex); + watcherStarted = true; + } +} + + +void QSymbianFileSystemWatcherEngine::run() +{ + mutex.lock(); + syncCondition.wakeOne(); + mutex.unlock(); + + exec(); + + foreach(QNotifyChangeEvent *e, activeObjectToPath.keys()) { + e->Cancel(); + delete e; + } + + activeObjectToPath.clear(); +} + +void QSymbianFileSystemWatcherEngine::addNativeListener(const QString &directoryPath) +{ + QMutexLocker locker(&mutex); + QString nativeDir(QDir::toNativeSeparators(directoryPath)); + TPtrC ptr(qt_QString2TPtrC(nativeDir)); + currentAddEvent = new QNotifyChangeEvent(qt_s60GetRFs(), ptr, this, directoryPath.endsWith(QChar(L'/'), Qt::CaseSensitive)); + syncCondition.wakeOne(); +} + +void QSymbianFileSystemWatcherEngine::removeNativeListener() +{ + QMutexLocker locker(&mutex); + currentRemoveEvent->Cancel(); + delete currentRemoveEvent; + currentRemoveEvent = NULL; + syncCondition.wakeOne(); +} + + +QT_END_NAMESPACE +#endif // QT_NO_FILESYSTEMWATCHER diff --git a/src/corelib/io/qfilesystemwatcher_symbian_p.h b/src/corelib/io/qfilesystemwatcher_symbian_p.h new file mode 100644 index 0000000000..d642e3a382 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_symbian_p.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMWATCHER_SYMBIAN_P_H +#define QFILESYSTEMWATCHER_SYMBIAN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qfilesystemwatcher_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include "qhash.h" +#include "qmutex.h" +#include "qwaitcondition.h" + +#include <e32base.h> +#include <f32file.h> + +QT_BEGIN_NAMESPACE + +class QSymbianFileSystemWatcherEngine; + +class QNotifyChangeEvent : public CActive +{ +public: + QNotifyChangeEvent(RFs &fsSession, const TDesC &file, QSymbianFileSystemWatcherEngine *engine, + bool aIsDir, TInt aPriority = EPriorityStandard); + ~QNotifyChangeEvent(); + + bool isDir; + +private: + void RunL(); + void DoCancel(); + + RFs &fsSession; + TPath watchedPath; + QSymbianFileSystemWatcherEngine *engine; + + int failureCount; +}; + +class QSymbianFileSystemWatcherEngine : public QFileSystemWatcherEngine +{ + Q_OBJECT + +public: + QSymbianFileSystemWatcherEngine(); + ~QSymbianFileSystemWatcherEngine(); + + QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories); + QStringList removePaths(const QStringList &paths, QStringList *files, + QStringList *directories); + + void stop(); + +protected: + void run(); + +public Q_SLOTS: + void addNativeListener(const QString &directoryPath); + void removeNativeListener(); + +private: + friend class QNotifyChangeEvent; + void emitPathChanged(QNotifyChangeEvent *e); + + void startWatcher(); + + QHash<QNotifyChangeEvent*, QString> activeObjectToPath; + QMutex mutex; + QWaitCondition syncCondition; + bool watcherStarted; + QNotifyChangeEvent *currentAddEvent; + QNotifyChangeEvent *currentRemoveEvent; +}; + +#endif // QT_NO_FILESYSTEMWATCHER + +QT_END_NAMESPACE + +#endif // QFILESYSTEMWATCHER_WIN_P_H diff --git a/src/corelib/io/qfilesystemwatcher_win.cpp b/src/corelib/io/qfilesystemwatcher_win.cpp new file mode 100644 index 0000000000..26b83b21dc --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_win.cpp @@ -0,0 +1,425 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemwatcher.h" +#include "qfilesystemwatcher_win_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <qdebug.h> +#include <qfileinfo.h> +#include <qstringlist.h> +#include <qset.h> +#include <qdatetime.h> +#include <qdir.h> + +QT_BEGIN_NAMESPACE + +void QWindowsFileSystemWatcherEngine::stop() +{ + foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) + thread->stop(); +} + +QWindowsFileSystemWatcherEngine::QWindowsFileSystemWatcherEngine() + : QFileSystemWatcherEngine(false) +{ +} + +QWindowsFileSystemWatcherEngine::~QWindowsFileSystemWatcherEngine() +{ + if (threads.isEmpty()) + return; + + foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) { + thread->stop(); + thread->wait(); + delete thread; + } +} + +QStringList QWindowsFileSystemWatcherEngine::addPaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ + // qDebug()<<"Adding"<<paths.count()<<"to existing"<<(files->count() + directories->count())<<"watchers"; + QStringList p = paths; + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + QString normalPath = path; + if ((normalPath.endsWith(QLatin1Char('/')) && !normalPath.endsWith(QLatin1String(":/"))) + || (normalPath.endsWith(QLatin1Char('\\')) && !normalPath.endsWith(QLatin1String(":\\"))) +#ifdef Q_OS_WINCE + && normalPath.size() > 1) +#else + ) +#endif + normalPath.chop(1); + QFileInfo fileInfo(normalPath.toLower()); + if (!fileInfo.exists()) + continue; + + bool isDir = fileInfo.isDir(); + if (isDir) { + if (directories->contains(path)) + continue; + } else { + if (files->contains(path)) + continue; + } + + // qDebug()<<"Looking for a thread/handle for"<<normalPath; + + const QString absolutePath = isDir ? fileInfo.absoluteFilePath() : fileInfo.absolutePath(); + const uint flags = isDir + ? (FILE_NOTIFY_CHANGE_DIR_NAME + | FILE_NOTIFY_CHANGE_FILE_NAME) + : (FILE_NOTIFY_CHANGE_DIR_NAME + | FILE_NOTIFY_CHANGE_FILE_NAME + | FILE_NOTIFY_CHANGE_ATTRIBUTES + | FILE_NOTIFY_CHANGE_SIZE + | FILE_NOTIFY_CHANGE_LAST_WRITE + | FILE_NOTIFY_CHANGE_SECURITY); + + QWindowsFileSystemWatcherEngine::PathInfo pathInfo; + pathInfo.absolutePath = absolutePath; + pathInfo.isDir = isDir; + pathInfo.path = path; + pathInfo = fileInfo; + + // Look for a thread + QWindowsFileSystemWatcherEngineThread *thread = 0; + QWindowsFileSystemWatcherEngine::Handle handle; + QList<QWindowsFileSystemWatcherEngineThread *>::const_iterator jt, end; + end = threads.constEnd(); + for(jt = threads.constBegin(); jt != end; ++jt) { + thread = *jt; + QMutexLocker locker(&(thread->mutex)); + + handle = thread->handleForDir.value(absolutePath); + if (handle.handle != INVALID_HANDLE_VALUE && handle.flags == flags) { + // found a thread now insert... + // qDebug()<<" Found a thread"<<thread; + + QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo> &h + = thread->pathInfoForHandle[handle.handle]; + if (!h.contains(fileInfo.absoluteFilePath())) { + thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo); + if (isDir) + directories->append(path); + else + files->append(path); + } + it.remove(); + thread->wakeup(); + break; + } + } + + // no thread found, first create a handle + if (handle.handle == INVALID_HANDLE_VALUE || handle.flags != flags) { + // qDebug()<<" No thread found"; + // Volume and folder paths need a trailing slash for proper notification + // (e.g. "c:" -> "c:/"). + const QString effectiveAbsolutePath = + isDir ? (absolutePath + QLatin1Char('/')) : absolutePath; + + handle.handle = FindFirstChangeNotification((wchar_t*) QDir::toNativeSeparators(effectiveAbsolutePath).utf16(), false, flags); + handle.flags = flags; + if (handle.handle == INVALID_HANDLE_VALUE) + continue; + + // now look for a thread to insert + bool found = false; + foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) { + QMutexLocker(&(thread->mutex)); + if (thread->handles.count() < MAXIMUM_WAIT_OBJECTS) { + // qDebug() << " Added handle" << handle.handle << "for" << absolutePath << "to watch" << fileInfo.absoluteFilePath(); + // qDebug()<< " to existing thread"<<thread; + thread->handles.append(handle.handle); + thread->handleForDir.insert(absolutePath, handle); + + thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo); + if (isDir) + directories->append(path); + else + files->append(path); + + it.remove(); + found = true; + thread->wakeup(); + break; + } + } + if (!found) { + QWindowsFileSystemWatcherEngineThread *thread = new QWindowsFileSystemWatcherEngineThread(); + //qDebug()<<" ###Creating new thread"<<thread<<"("<<(threads.count()+1)<<"threads)"; + thread->handles.append(handle.handle); + thread->handleForDir.insert(absolutePath, handle); + + thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo); + if (isDir) + directories->append(path); + else + files->append(path); + + connect(thread, SIGNAL(fileChanged(QString,bool)), + this, SIGNAL(fileChanged(QString,bool))); + connect(thread, SIGNAL(directoryChanged(QString,bool)), + this, SIGNAL(directoryChanged(QString,bool))); + + thread->msg = '@'; + thread->start(); + threads.append(thread); + it.remove(); + } + } + } + return p; +} + +QStringList QWindowsFileSystemWatcherEngine::removePaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ + // qDebug()<<"removePaths"<<paths; + QStringList p = paths; + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + QString normalPath = path; + if (normalPath.endsWith(QLatin1Char('/')) || normalPath.endsWith(QLatin1Char('\\'))) + normalPath.chop(1); + QFileInfo fileInfo(normalPath.toLower()); + // qDebug()<<"removing"<<normalPath; + QString absolutePath = fileInfo.absoluteFilePath(); + QList<QWindowsFileSystemWatcherEngineThread *>::iterator jt, end; + end = threads.end(); + for(jt = threads.begin(); jt!= end; ++jt) { + QWindowsFileSystemWatcherEngineThread *thread = *jt; + if (*jt == 0) + continue; + + QMutexLocker locker(&(thread->mutex)); + + QWindowsFileSystemWatcherEngine::Handle handle = thread->handleForDir.value(absolutePath); + if (handle.handle == INVALID_HANDLE_VALUE) { + // perhaps path is a file? + absolutePath = fileInfo.absolutePath(); + handle = thread->handleForDir.value(absolutePath); + } + if (handle.handle != INVALID_HANDLE_VALUE) { + QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo> &h = + thread->pathInfoForHandle[handle.handle]; + if (h.remove(fileInfo.absoluteFilePath())) { + // ### + files->removeAll(path); + directories->removeAll(path); + + if (h.isEmpty()) { + // qDebug() << "Closing handle" << handle.handle; + FindCloseChangeNotification(handle.handle); // This one might generate a notification + + int indexOfHandle = thread->handles.indexOf(handle.handle); + Q_ASSERT(indexOfHandle != -1); + thread->handles.remove(indexOfHandle); + + thread->handleForDir.remove(absolutePath); + // h is now invalid + + it.remove(); + + if (thread->handleForDir.isEmpty()) { + // qDebug()<<"Stopping thread "<<thread; + locker.unlock(); + thread->stop(); + thread->wait(); + locker.relock(); + // We can't delete the thread until the mutex locker is + // out of scope + } + } + } + // Found the file, go to next one + break; + } + } + } + + // Remove all threads that we stopped + QList<QWindowsFileSystemWatcherEngineThread *>::iterator jt, end; + end = threads.end(); + for(jt = threads.begin(); jt != end; ++jt) { + if (!(*jt)->isRunning()) { + delete *jt; + *jt = 0; + } + } + + threads.removeAll(0); + return p; +} + +/////////// +// QWindowsFileSystemWatcherEngineThread +/////////// + +QWindowsFileSystemWatcherEngineThread::QWindowsFileSystemWatcherEngineThread() + : msg(0) +{ + if (HANDLE h = CreateEvent(0, false, false, 0)) { + handles.reserve(MAXIMUM_WAIT_OBJECTS); + handles.append(h); + } + moveToThread(this); +} + + +QWindowsFileSystemWatcherEngineThread::~QWindowsFileSystemWatcherEngineThread() +{ + CloseHandle(handles.at(0)); + handles[0] = INVALID_HANDLE_VALUE; + + foreach (HANDLE h, handles) { + if (h == INVALID_HANDLE_VALUE) + continue; + FindCloseChangeNotification(h); + } +} + +void QWindowsFileSystemWatcherEngineThread::run() +{ + QMutexLocker locker(&mutex); + forever { + QVector<HANDLE> handlesCopy = handles; + locker.unlock(); + // qDebug() << "QWindowsFileSystemWatcherThread"<<this<<"waiting on" << handlesCopy.count() << "handles"; + DWORD r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, INFINITE); + locker.relock(); + do { + if (r == WAIT_OBJECT_0) { + int m = msg; + msg = 0; + if (m == 'q') { + // qDebug() << "thread"<<this<<"told to quit"; + return; + } + if (m != '@') { + qDebug("QWindowsFileSystemWatcherEngine: unknown message '%c' send to thread", char(m)); + } + break; + } else if (r > WAIT_OBJECT_0 && r < WAIT_OBJECT_0 + uint(handlesCopy.count())) { + int at = r - WAIT_OBJECT_0; + Q_ASSERT(at < handlesCopy.count()); + HANDLE handle = handlesCopy.at(at); + + // When removing a path, FindCloseChangeNotification might actually fire a notification + // for some reason, so we must check if the handle exist in the handles vector + if (handles.contains(handle)) { + // qDebug()<<"thread"<<this<<"Acknowledged handle:"<<at<<handle; + if (!FindNextChangeNotification(handle)) { + qErrnoWarning("QFileSystemWatcher: FindNextChangeNotification failed!!"); + } + + QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo> &h = pathInfoForHandle[handle]; + QMutableHashIterator<QString, QWindowsFileSystemWatcherEngine::PathInfo> it(h); + while (it.hasNext()) { + QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo>::iterator x = it.next(); + QString absolutePath = x.value().absolutePath; + QFileInfo fileInfo(x.value().path); + // qDebug() << "checking" << x.key(); + if (!fileInfo.exists()) { + // qDebug() << x.key() << "removed!"; + if (x.value().isDir) + emit directoryChanged(x.value().path, true); + else + emit fileChanged(x.value().path, true); + h.erase(x); + + // close the notification handle if the directory has been removed + if (h.isEmpty()) { + // qDebug() << "Thread closing handle" << handle; + FindCloseChangeNotification(handle); // This one might generate a notification + + int indexOfHandle = handles.indexOf(handle); + Q_ASSERT(indexOfHandle != -1); + handles.remove(indexOfHandle); + + handleForDir.remove(absolutePath); + // h is now invalid + } + } else if (x.value().isDir) { + // qDebug() << x.key() << "directory changed!"; + emit directoryChanged(x.value().path, false); + x.value() = fileInfo; + } else if (x.value() != fileInfo) { + // qDebug() << x.key() << "file changed!"; + emit fileChanged(x.value().path, false); + x.value() = fileInfo; + } + } + } + } else { + // qErrnoWarning("QFileSystemWatcher: error while waiting for change notification"); + break; // avoid endless loop + } + handlesCopy = handles; + r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, 0); + } while (r != WAIT_TIMEOUT); + } +} + + +void QWindowsFileSystemWatcherEngineThread::stop() +{ + msg = 'q'; + SetEvent(handles.at(0)); +} + +void QWindowsFileSystemWatcherEngineThread::wakeup() +{ + msg = '@'; + SetEvent(handles.at(0)); +} + +QT_END_NAMESPACE +#endif // QT_NO_FILESYSTEMWATCHER diff --git a/src/corelib/io/qfilesystemwatcher_win_p.h b/src/corelib/io/qfilesystemwatcher_win_p.h new file mode 100644 index 0000000000..4f00abfa00 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_win_p.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMWATCHER_WIN_P_H +#define QFILESYSTEMWATCHER_WIN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qfilesystemwatcher_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <qt_windows.h> + +#include <QtCore/qdatetime.h> +#include <QtCore/qfile.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qhash.h> +#include <QtCore/qmutex.h> +#include <QtCore/qvector.h> + +QT_BEGIN_NAMESPACE + +class QWindowsFileSystemWatcherEngineThread; + +// Even though QWindowsFileSystemWatcherEngine is derived of QThread +// via QFileSystemWatcher, it does not start a thread. +// Instead QWindowsFileSystemWatcher creates QWindowsFileSystemWatcherEngineThreads +// to do the actually watching. +class QWindowsFileSystemWatcherEngine : public QFileSystemWatcherEngine +{ + Q_OBJECT +public: + QWindowsFileSystemWatcherEngine(); + ~QWindowsFileSystemWatcherEngine(); + + QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories); + QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories); + + void stop(); + + + class Handle + { + public: + HANDLE handle; + uint flags; + + Handle() + : handle(INVALID_HANDLE_VALUE), flags(0u) + { } + Handle(const Handle &other) + : handle(other.handle), flags(other.flags) + { } + }; + + class PathInfo { + public: + QString absolutePath; + QString path; + bool isDir; + + // fileinfo bits + uint ownerId; + uint groupId; + QFile::Permissions permissions; + QDateTime lastModified; + + PathInfo &operator=(const QFileInfo &fileInfo) + { + ownerId = fileInfo.ownerId(); + groupId = fileInfo.groupId(); + permissions = fileInfo.permissions(); + lastModified = fileInfo.lastModified(); + return *this; + } + + bool operator!=(const QFileInfo &fileInfo) const + { + return (ownerId != fileInfo.ownerId() + || groupId != fileInfo.groupId() + || permissions != fileInfo.permissions() + || lastModified != fileInfo.lastModified()); + } + }; +private: + QList<QWindowsFileSystemWatcherEngineThread *> threads; + +}; + +class QWindowsFileSystemWatcherEngineThread : public QThread +{ + Q_OBJECT + +public: + QWindowsFileSystemWatcherEngineThread(); + ~QWindowsFileSystemWatcherEngineThread(); + void run(); + void stop(); + void wakeup(); + + QMutex mutex; + QVector<HANDLE> handles; + int msg; + + QHash<QString, QWindowsFileSystemWatcherEngine::Handle> handleForDir; + + QHash<HANDLE, QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo> > pathInfoForHandle; + +Q_SIGNALS: + void fileChanged(const QString &path, bool removed); + void directoryChanged(const QString &path, bool removed); +}; + +#endif // QT_NO_FILESYSTEMWATCHER + +QT_END_NAMESPACE + +#endif // QFILESYSTEMWATCHER_WIN_P_H diff --git a/src/corelib/io/qfsfileengine.cpp b/src/corelib/io/qfsfileengine.cpp new file mode 100644 index 0000000000..802d394af1 --- /dev/null +++ b/src/corelib/io/qfsfileengine.cpp @@ -0,0 +1,965 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfsfileengine_p.h" +#include "qfsfileengine_iterator_p.h" +#include "qfilesystemengine_p.h" +#include "qdatetime.h" +#include "qdiriterator.h" +#include "qset.h" +#include <QtCore/qdebug.h> + +#ifndef QT_NO_FSFILEENGINE + +#if !defined(Q_OS_WINCE) +#include <errno.h> +#endif +#if defined(Q_OS_UNIX) +#include "private/qcore_unix_p.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#if defined(Q_OS_MAC) +# include <private/qcore_mac_p.h> +#endif + +QT_BEGIN_NAMESPACE + +#ifdef Q_OS_WIN +# ifndef S_ISREG +# define S_ISREG(x) (((x) & S_IFMT) == S_IFREG) +# endif +# ifndef S_ISCHR +# define S_ISCHR(x) (((x) & S_IFMT) == S_IFCHR) +# endif +# ifndef S_ISFIFO +# define S_ISFIFO(x) false +# endif +# ifndef S_ISSOCK +# define S_ISSOCK(x) false +# endif +# ifndef INVALID_FILE_ATTRIBUTES +# define INVALID_FILE_ATTRIBUTES (DWORD (-1)) +# endif +#endif + +/*! \class QFSFileEngine + \brief The QFSFileEngine class implements Qt's default file engine. + \since 4.1 + + This class is part of the file engine framework in Qt. If you only want to + access files or directories, use QFile, QFileInfo or QDir instead. + + QFSFileEngine is the default file engine for accessing regular files. It + is provided for convenience; by subclassing this class, you can alter its + behavior slightly, without having to write a complete QAbstractFileEngine + subclass. To install your custom file engine, you must also subclass + QAbstractFileEngineHandler and create an instance of your handler. + + It can also be useful to create a QFSFileEngine object directly if you + need to use the local file system inside QAbstractFileEngine::create(), in + order to avoid recursion (as higher-level classes tend to call + QAbstractFileEngine::create()). +*/ + +//**************** QFSFileEnginePrivate +QFSFileEnginePrivate::QFSFileEnginePrivate() : QAbstractFileEnginePrivate() +{ + init(); +} + +/*! + \internal +*/ +void QFSFileEnginePrivate::init() +{ + is_sequential = 0; + tried_stat = 0; +#if !defined(Q_OS_WINCE) + need_lstat = 1; + is_link = 0; +#endif + openMode = QIODevice::NotOpen; + fd = -1; + fh = 0; +#ifdef Q_OS_SYMBIAN + fileHandleForMaps = -1; +#endif + lastIOCommand = IOFlushCommand; + lastFlushFailed = false; + closeFileHandle = false; +#ifdef Q_OS_WIN + fileAttrib = INVALID_FILE_ATTRIBUTES; + fileHandle = INVALID_HANDLE_VALUE; + mapHandle = INVALID_HANDLE_VALUE; +#ifndef Q_OS_WINCE + cachedFd = -1; +#endif +#endif +} + +/*! + Constructs a QFSFileEngine for the file name \a file. +*/ +QFSFileEngine::QFSFileEngine(const QString &file) + : QAbstractFileEngine(*new QFSFileEnginePrivate) +{ + Q_D(QFSFileEngine); + d->fileEntry = QFileSystemEntry(file); +} + +/*! + Constructs a QFSFileEngine. +*/ +QFSFileEngine::QFSFileEngine() : QAbstractFileEngine(*new QFSFileEnginePrivate) +{ +} + +/*! + \internal +*/ +QFSFileEngine::QFSFileEngine(QFSFileEnginePrivate &dd) + : QAbstractFileEngine(dd) +{ +} + +/*! + Destructs the QFSFileEngine. +*/ +QFSFileEngine::~QFSFileEngine() +{ + Q_D(QFSFileEngine); + if (d->closeFileHandle) { + if (d->fh) { + int ret; + do { + ret = fclose(d->fh); + } while (ret == EOF && errno == EINTR); + } else if (d->fd != -1) { + int ret; + do { + ret = QT_CLOSE(d->fd); + } while (ret == -1 && errno == EINTR); + } + } + QList<uchar*> keys = d->maps.keys(); + for (int i = 0; i < keys.count(); ++i) + unmap(keys.at(i)); +} + +/*! + \reimp +*/ +void QFSFileEngine::setFileName(const QString &file) +{ + Q_D(QFSFileEngine); + d->init(); + d->fileEntry = QFileSystemEntry(file); +} + +/*! + \reimp +*/ +bool QFSFileEngine::open(QIODevice::OpenMode openMode) +{ + Q_D(QFSFileEngine); + if (d->fileEntry.isEmpty()) { + qWarning("QFSFileEngine::open: No file name specified"); + setError(QFile::OpenError, QLatin1String("No file name specified")); + return false; + } + + // Append implies WriteOnly. + if (openMode & QFile::Append) + openMode |= QFile::WriteOnly; + + // WriteOnly implies Truncate if neither ReadOnly nor Append are sent. + if ((openMode & QFile::WriteOnly) && !(openMode & (QFile::ReadOnly | QFile::Append))) + openMode |= QFile::Truncate; + + d->openMode = openMode; + d->lastFlushFailed = false; + d->tried_stat = 0; + d->fh = 0; + d->fd = -1; + + return d->nativeOpen(openMode); +} + +/*! + Opens the file handle \a fh in \a openMode mode. Returns true on + success; otherwise returns false. +*/ +bool QFSFileEngine::open(QIODevice::OpenMode openMode, FILE *fh) +{ + return open(openMode, fh, QFile::DontCloseHandle); +} + +bool QFSFileEngine::open(QIODevice::OpenMode openMode, FILE *fh, QFile::FileHandleFlags handleFlags) +{ + Q_D(QFSFileEngine); + + // Append implies WriteOnly. + if (openMode & QFile::Append) + openMode |= QFile::WriteOnly; + + // WriteOnly implies Truncate if neither ReadOnly nor Append are sent. + if ((openMode & QFile::WriteOnly) && !(openMode & (QFile::ReadOnly | QFile::Append))) + openMode |= QFile::Truncate; + + d->openMode = openMode; + d->lastFlushFailed = false; + d->closeFileHandle = (handleFlags & QFile::AutoCloseHandle); + d->fileEntry.clear(); + d->tried_stat = 0; + d->fd = -1; + + return d->openFh(openMode, fh); +} + +/*! + Opens the file handle \a fh using the open mode \a flags. +*/ +bool QFSFileEnginePrivate::openFh(QIODevice::OpenMode openMode, FILE *fh) +{ + Q_Q(QFSFileEngine); + this->fh = fh; + fd = -1; + + // Seek to the end when in Append mode. + if (openMode & QIODevice::Append) { + int ret; + do { + ret = QT_FSEEK(fh, 0, SEEK_END); + } while (ret != 0 && errno == EINTR); + + if (ret != 0) { + q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, + qt_error_string(int(errno))); + + this->openMode = QIODevice::NotOpen; + this->fh = 0; + + return false; + } + } + + return true; +} + +/*! + Opens the file descriptor \a fd in \a openMode mode. Returns true + on success; otherwise returns false. +*/ +bool QFSFileEngine::open(QIODevice::OpenMode openMode, int fd) +{ + return open(openMode, fd, QFile::DontCloseHandle); +} + +bool QFSFileEngine::open(QIODevice::OpenMode openMode, int fd, QFile::FileHandleFlags handleFlags) +{ + Q_D(QFSFileEngine); + + // Append implies WriteOnly. + if (openMode & QFile::Append) + openMode |= QFile::WriteOnly; + + // WriteOnly implies Truncate if neither ReadOnly nor Append are sent. + if ((openMode & QFile::WriteOnly) && !(openMode & (QFile::ReadOnly | QFile::Append))) + openMode |= QFile::Truncate; + + d->openMode = openMode; + d->lastFlushFailed = false; + d->closeFileHandle = (handleFlags & QFile::AutoCloseHandle); + d->fileEntry.clear(); + d->fh = 0; + d->fd = -1; + d->tried_stat = 0; + + return d->openFd(openMode, fd); +} + + +/*! + Opens the file descriptor \a fd to the file engine, using the open mode \a + flags. +*/ +bool QFSFileEnginePrivate::openFd(QIODevice::OpenMode openMode, int fd) +{ + Q_Q(QFSFileEngine); + this->fd = fd; + fh = 0; + + // Seek to the end when in Append mode. + if (openMode & QFile::Append) { + int ret; + do { + ret = QT_LSEEK(fd, 0, SEEK_END); + } while (ret == -1 && errno == EINTR); + + if (ret == -1) { + q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, + qt_error_string(int(errno))); + + this->openMode = QIODevice::NotOpen; + this->fd = -1; + + return false; + } + } + + return true; +} + +/*! + \reimp +*/ +bool QFSFileEngine::close() +{ + Q_D(QFSFileEngine); + d->openMode = QIODevice::NotOpen; + return d->nativeClose(); +} + +/*! + \internal +*/ +bool QFSFileEnginePrivate::closeFdFh() +{ + Q_Q(QFSFileEngine); + if (fd == -1 && !fh +#ifdef Q_OS_SYMBIAN + && !symbianFile.SubSessionHandle() + && fileHandleForMaps == -1 +#endif + ) + return false; + + // Flush the file if it's buffered, and if the last flush didn't fail. + bool flushed = !fh || (!lastFlushFailed && q->flush()); + bool closed = true; + tried_stat = 0; + +#ifdef Q_OS_SYMBIAN + // Map handle is always owned by us so always close it + if (fileHandleForMaps >= 0) { + QT_CLOSE(fileHandleForMaps); + fileHandleForMaps = -1; + } +#endif + + // Close the file if we created the handle. + if (closeFileHandle) { + int ret; + do { +#ifdef Q_OS_SYMBIAN + if (symbianFile.SubSessionHandle()) { + symbianFile.Close(); + ret = 0; + } else +#endif + if (fh) { + // Close buffered file. + ret = fclose(fh) != 0 ? -1 : 0; + } else { + // Close unbuffered file. + ret = QT_CLOSE(fd); + } + } while (ret == -1 && errno == EINTR); + + // We must reset these guys regardless; calling close again after a + // failed close causes crashes on some systems. + fh = 0; + fd = -1; + closed = (ret == 0); + } + + // Report errors. + if (!flushed || !closed) { + if (flushed) { + // If not flushed, we want the flush error to fall through. + q->setError(QFile::UnspecifiedError, qt_error_string(errno)); + } + return false; + } + + return true; +} + +/*! + \reimp +*/ +bool QFSFileEngine::flush() +{ + Q_D(QFSFileEngine); + if ((d->openMode & QIODevice::WriteOnly) == 0) { + // Nothing in the write buffers, so flush succeeds in doing + // nothing. + return true; + } + return d->nativeFlush(); +} + +/*! + \internal +*/ +bool QFSFileEnginePrivate::flushFh() +{ + Q_Q(QFSFileEngine); + + // Never try to flush again if the last flush failed. Otherwise you can + // get crashes on some systems (AIX). + if (lastFlushFailed) + return false; + + int ret = fflush(fh); + + lastFlushFailed = (ret != 0); + lastIOCommand = QFSFileEnginePrivate::IOFlushCommand; + + if (ret != 0) { + q->setError(errno == ENOSPC ? QFile::ResourceError : QFile::WriteError, + qt_error_string(errno)); + return false; + } + return true; +} + +/*! + \reimp +*/ +qint64 QFSFileEngine::size() const +{ + Q_D(const QFSFileEngine); + return d->nativeSize(); +} + +/*! + \internal +*/ +#ifndef Q_OS_WIN +qint64 QFSFileEnginePrivate::sizeFdFh() const +{ + Q_Q(const QFSFileEngine); + const_cast<QFSFileEngine *>(q)->flush(); + + tried_stat = 0; + metaData.clearFlags(QFileSystemMetaData::SizeAttribute); + if (!doStat(QFileSystemMetaData::SizeAttribute)) + return 0; + return metaData.size(); +} +#endif + +/*! + \reimp +*/ +qint64 QFSFileEngine::pos() const +{ + Q_D(const QFSFileEngine); + return d->nativePos(); +} + +/*! + \internal +*/ +qint64 QFSFileEnginePrivate::posFdFh() const +{ + if (fh) + return qint64(QT_FTELL(fh)); + return QT_LSEEK(fd, 0, SEEK_CUR); +} + +/*! + \reimp +*/ +bool QFSFileEngine::seek(qint64 pos) +{ + Q_D(QFSFileEngine); + return d->nativeSeek(pos); +} + +/*! + \internal +*/ +bool QFSFileEnginePrivate::seekFdFh(qint64 pos) +{ + Q_Q(QFSFileEngine); + + // On Windows' stdlib implementation, the results of calling fread and + // fwrite are undefined if not called either in sequence, or if preceded + // with a call to fflush(). + if (lastIOCommand != QFSFileEnginePrivate::IOFlushCommand && !q->flush()) + return false; + + if (pos < 0 || pos != qint64(QT_OFF_T(pos))) + return false; + + if (fh) { + // Buffered stdlib mode. + int ret; + do { + ret = QT_FSEEK(fh, QT_OFF_T(pos), SEEK_SET); + } while (ret != 0 && errno == EINTR); + + if (ret != 0) { + q->setError(QFile::ReadError, qt_error_string(int(errno))); + return false; + } + } else { + // Unbuffered stdio mode. + if (QT_LSEEK(fd, QT_OFF_T(pos), SEEK_SET) == -1) { + qWarning() << "QFile::at: Cannot set file position" << pos; + q->setError(QFile::PositionError, qt_error_string(errno)); + return false; + } + } + return true; +} + +/*! + \reimp +*/ +int QFSFileEngine::handle() const +{ + Q_D(const QFSFileEngine); + return d->nativeHandle(); +} + +/*! + \reimp +*/ +qint64 QFSFileEngine::read(char *data, qint64 maxlen) +{ + Q_D(QFSFileEngine); + + // On Windows' stdlib implementation, the results of calling fread and + // fwrite are undefined if not called either in sequence, or if preceded + // with a call to fflush(). + if (d->lastIOCommand != QFSFileEnginePrivate::IOReadCommand) { + flush(); + d->lastIOCommand = QFSFileEnginePrivate::IOReadCommand; + } + + return d->nativeRead(data, maxlen); +} + +/*! + \internal +*/ +qint64 QFSFileEnginePrivate::readFdFh(char *data, qint64 len) +{ + Q_Q(QFSFileEngine); + + if (len < 0 || len != qint64(size_t(len))) { + q->setError(QFile::ReadError, qt_error_string(EINVAL)); + return -1; + } + + qint64 readBytes = 0; + bool eof = false; + + if (fh) { + // Buffered stdlib mode. + + size_t result; + bool retry = true; + do { + result = fread(data + readBytes, 1, size_t(len - readBytes), fh); + eof = feof(fh); + if (retry && eof && result == 0) { + // On Mac OS, this is needed, e.g., if a file was written to + // through another stream since our last read. See test + // tst_QFile::appendAndRead + QT_FSEEK(fh, QT_FTELL(fh), SEEK_SET); // re-sync stream. + retry = false; + continue; + } + readBytes += result; + } while (!eof && (result == 0 ? errno == EINTR : readBytes < len)); + + } else if (fd != -1) { + // Unbuffered stdio mode. + +#ifdef Q_OS_WIN + int result; +#else + ssize_t result; +#endif + do { + result = QT_READ(fd, data + readBytes, size_t(len - readBytes)); + } while ((result == -1 && errno == EINTR) + || (result > 0 && (readBytes += result) < len)); + + eof = !(result == -1); + } + + if (!eof && readBytes == 0) { + readBytes = -1; + q->setError(QFile::ReadError, qt_error_string(errno)); + } + + return readBytes; +} + +/*! + \reimp +*/ +qint64 QFSFileEngine::readLine(char *data, qint64 maxlen) +{ + Q_D(QFSFileEngine); + + // On Windows' stdlib implementation, the results of calling fread and + // fwrite are undefined if not called either in sequence, or if preceded + // with a call to fflush(). + if (d->lastIOCommand != QFSFileEnginePrivate::IOReadCommand) { + flush(); + d->lastIOCommand = QFSFileEnginePrivate::IOReadCommand; + } + + return d->nativeReadLine(data, maxlen); +} + +/*! + \internal +*/ +qint64 QFSFileEnginePrivate::readLineFdFh(char *data, qint64 maxlen) +{ + Q_Q(QFSFileEngine); + if (!fh) + return q->QAbstractFileEngine::readLine(data, maxlen); + + QT_OFF_T oldPos = 0; +#ifdef Q_OS_WIN + bool seq = q->isSequential(); + if (!seq) +#endif + oldPos = QT_FTELL(fh); + + // QIODevice::readLine() passes maxlen - 1 to QFile::readLineData() + // because it has made space for the '\0' at the end of data. But fgets + // does the same, so we'd get two '\0' at the end - passing maxlen + 1 + // solves this. + if (!fgets(data, int(maxlen + 1), fh)) { + if (!feof(fh)) + q->setError(QFile::ReadError, qt_error_string(int(errno))); + return -1; // error + } + +#ifdef Q_OS_WIN + if (seq) + return qstrlen(data); +#endif + + qint64 lineLength = QT_FTELL(fh) - oldPos; + return lineLength > 0 ? lineLength : qstrlen(data); +} + +/*! + \reimp +*/ +qint64 QFSFileEngine::write(const char *data, qint64 len) +{ + Q_D(QFSFileEngine); + + // On Windows' stdlib implementation, the results of calling fread and + // fwrite are undefined if not called either in sequence, or if preceded + // with a call to fflush(). + if (d->lastIOCommand != QFSFileEnginePrivate::IOWriteCommand) { + flush(); + d->lastIOCommand = QFSFileEnginePrivate::IOWriteCommand; + } + + return d->nativeWrite(data, len); +} + +/*! + \internal +*/ +qint64 QFSFileEnginePrivate::writeFdFh(const char *data, qint64 len) +{ + Q_Q(QFSFileEngine); + + if (len < 0 || len != qint64(size_t(len))) { + q->setError(QFile::WriteError, qt_error_string(EINVAL)); + return -1; + } + + qint64 writtenBytes = 0; + + if (fh) { + // Buffered stdlib mode. + + size_t result; + do { + result = fwrite(data + writtenBytes, 1, size_t(len - writtenBytes), fh); + writtenBytes += result; + } while (result == 0 ? errno == EINTR : writtenBytes < len); + + } else if (fd != -1) { + // Unbuffered stdio mode. + +#ifdef Q_OS_WIN + int result; +#else + ssize_t result; +#endif + do { + result = QT_WRITE(fd, data + writtenBytes, size_t(len - writtenBytes)); + } while ((result == -1 && errno == EINTR) + || (result > 0 && (writtenBytes += result) < len)); + } + + if (len && writtenBytes == 0) { + writtenBytes = -1; + q->setError(errno == ENOSPC ? QFile::ResourceError : QFile::WriteError, qt_error_string(errno)); + } + + return writtenBytes; +} + +/*! + \internal +*/ +QAbstractFileEngine::Iterator *QFSFileEngine::beginEntryList(QDir::Filters filters, const QStringList &filterNames) +{ + return new QFSFileEngineIterator(filters, filterNames); +} + +/*! + \internal +*/ +QAbstractFileEngine::Iterator *QFSFileEngine::endEntryList() +{ + return 0; +} + +/*! + \internal +*/ +QStringList QFSFileEngine::entryList(QDir::Filters filters, const QStringList &filterNames) const +{ + return QAbstractFileEngine::entryList(filters, filterNames); +} + +/*! + \reimp +*/ +bool QFSFileEngine::isSequential() const +{ + Q_D(const QFSFileEngine); + if (d->is_sequential == 0) + d->is_sequential = d->nativeIsSequential() ? 1 : 2; + return d->is_sequential == 1; +} + +/*! + \internal +*/ +#ifdef Q_OS_UNIX +bool QFSFileEnginePrivate::isSequentialFdFh() const +{ + if (doStat(QFileSystemMetaData::SequentialType)) + return metaData.isSequential(); + return true; +} +#endif + +/*! + \reimp +*/ +bool QFSFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) +{ + Q_D(QFSFileEngine); + if (extension == AtEndExtension && d->fh && isSequential()) + return feof(d->fh); + + if (extension == MapExtension) { + const MapExtensionOption *options = (MapExtensionOption*)(option); + MapExtensionReturn *returnValue = static_cast<MapExtensionReturn*>(output); + returnValue->address = d->map(options->offset, options->size, options->flags); + return (returnValue->address != 0); + } + if (extension == UnMapExtension) { + UnMapExtensionOption *options = (UnMapExtensionOption*)option; + return d->unmap(options->address); + } + + return false; +} + +/*! + \reimp +*/ +bool QFSFileEngine::supportsExtension(Extension extension) const +{ + Q_D(const QFSFileEngine); + if (extension == AtEndExtension && d->fh && isSequential()) + return true; + if (extension == FastReadLineExtension && d->fh) + return true; + if (extension == FastReadLineExtension && d->fd != -1 && isSequential()) + return true; + if (extension == UnMapExtension || extension == MapExtension) + return true; + return false; +} + +/*! \fn bool QFSFileEngine::caseSensitive() const + Returns true for Windows, false for Unix. +*/ + +/*! \fn bool QFSFileEngine::copy(const QString ©Name) + + For windows, copy the file to file \a copyName. + + Not implemented for Unix. +*/ + +/*! \fn QString QFSFileEngine::currentPath(const QString &fileName) + For Unix, returns the current working directory for the file + engine. + + For Windows, returns the canonicalized form of the current path used + by the file engine for the drive specified by \a fileName. On + Windows, each drive has its own current directory, so a different + path is returned for file names that include different drive names + (e.g. A: or C:). + + \sa setCurrentPath() +*/ + +/*! \fn QFileInfoList QFSFileEngine::drives() + For Windows, returns the list of drives in the file system as a list + of QFileInfo objects. On unix, Mac OS X and Windows CE, only the + root path is returned. On Windows, this function returns all drives + (A:\, C:\, D:\, etc.). + + For Unix, the list contains just the root path "/". +*/ + +/*! \fn QString QFSFileEngine::fileName(FileName file) const + \reimp +*/ + +/*! \fn QDateTime QFSFileEngine::fileTime(FileTime time) const + \reimp +*/ + +/*! \fn QString QFSFileEngine::homePath() + Returns the home path of the current user. + + \sa rootPath() +*/ + +/*! \fn bool QFSFileEngine::isRelativePath() const + \reimp +*/ + +/*! \fn bool QFSFileEngine::link(const QString &newName) + + Creates a link from the file currently specified by fileName() to + \a newName. What a link is depends on the underlying filesystem + (be it a shortcut on Windows or a symbolic link on Unix). Returns + true if successful; otherwise returns false. +*/ + +/*! \fn bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const + \reimp +*/ + +/*! \fn uint QFSFileEngine::ownerId(FileOwner own) const + In Unix, if stat() is successful, the \c uid is returned if + \a own is the owner. Otherwise the \c gid is returned. If stat() + is unsuccessful, -2 is reuturned. + + For Windows, -2 is always returned. +*/ + +/*! \fn QString QFSFileEngine::owner(FileOwner own) const + \reimp +*/ + +/*! \fn bool QFSFileEngine::remove() + \reimp +*/ + +/*! \fn bool QFSFileEngine::rename(const QString &newName) + \reimp +*/ + +/*! \fn bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const + \reimp +*/ + +/*! \fn QString QFSFileEngine::rootPath() + Returns the root path. + + \sa homePath() +*/ + +/*! \fn bool QFSFileEngine::setCurrentPath(const QString &path) + Sets the current path (e.g., for QDir), to \a path. Returns true if the + new path exists; otherwise this function does nothing, and returns false. + + \sa currentPath() +*/ + +/*! \fn bool QFSFileEngine::setPermissions(uint perms) + \reimp +*/ + +/*! \fn bool QFSFileEngine::setSize(qint64 size) + \reimp +*/ + +/*! \fn QString QFSFileEngine::tempPath() + Returns the temporary path (i.e., a path in which it is safe + to store temporary files). +*/ + +/*! \fn QAbstractFileEngine::FileFlags QFSFileEnginePrivate::getPermissions(QAbstractFileEngine::FileFlags type) const + \internal +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_FSFILEENGINE diff --git a/src/corelib/io/qfsfileengine.h b/src/corelib/io/qfsfileengine.h new file mode 100644 index 0000000000..726d5814f8 --- /dev/null +++ b/src/corelib/io/qfsfileengine.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFSFILEENGINE_H +#define QFSFILEENGINE_H + +#include <QtCore/qabstractfileengine.h> +#ifdef Q_OS_SYMBIAN +#include <f32file.h> +#endif + +#ifndef QT_NO_FSFILEENGINE + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QFSFileEnginePrivate; + +class Q_CORE_EXPORT QFSFileEngine : public QAbstractFileEngine +{ + Q_DECLARE_PRIVATE(QFSFileEngine) +public: + QFSFileEngine(); + explicit QFSFileEngine(const QString &file); + ~QFSFileEngine(); + + bool open(QIODevice::OpenMode openMode); + bool open(QIODevice::OpenMode flags, FILE *fh); + bool close(); + bool flush(); + qint64 size() const; + qint64 pos() const; + bool seek(qint64); + bool isSequential() const; + bool remove(); + bool copy(const QString &newName); + bool rename(const QString &newName); + bool link(const QString &newName); + bool mkdir(const QString &dirName, bool createParentDirectories) const; + bool rmdir(const QString &dirName, bool recurseParentDirectories) const; + bool setSize(qint64 size); + bool caseSensitive() const; + bool isRelativePath() const; + QStringList entryList(QDir::Filters filters, const QStringList &filterNames) const; + FileFlags fileFlags(FileFlags type) const; + bool setPermissions(uint perms); + QString fileName(FileName file) const; + uint ownerId(FileOwner) const; + QString owner(FileOwner) const; + QDateTime fileTime(FileTime time) const; + void setFileName(const QString &file); + int handle() const; + + Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames); + Iterator *endEntryList(); + + qint64 read(char *data, qint64 maxlen); + qint64 readLine(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + + bool extension(Extension extension, const ExtensionOption *option = 0, ExtensionReturn *output = 0); + bool supportsExtension(Extension extension) const; + + //FS only!! + bool open(QIODevice::OpenMode flags, int fd); + bool open(QIODevice::OpenMode flags, int fd, QFile::FileHandleFlags handleFlags); + bool open(QIODevice::OpenMode flags, FILE *fh, QFile::FileHandleFlags handleFlags); +#ifdef Q_OS_SYMBIAN + bool open(QIODevice::OpenMode flags, const RFile &f, QFile::FileHandleFlags handleFlags); +#endif + static bool setCurrentPath(const QString &path); + static QString currentPath(const QString &path = QString()); + static QString homePath(); + static QString rootPath(); + static QString tempPath(); + static QFileInfoList drives(); + +protected: + QFSFileEngine(QFSFileEnginePrivate &dd); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_FSFILEENGINE + +#endif // QFSFILEENGINE_H diff --git a/src/corelib/io/qfsfileengine_iterator.cpp b/src/corelib/io/qfsfileengine_iterator.cpp new file mode 100644 index 0000000000..d4c0e3f193 --- /dev/null +++ b/src/corelib/io/qfsfileengine_iterator.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfsfileengine_iterator_p.h" +#include "qfileinfo_p.h" +#include "qvariant.h" + +#ifndef QT_NO_FSFILEENGINE + +QT_BEGIN_NAMESPACE + +QFSFileEngineIterator::QFSFileEngineIterator(QDir::Filters filters, const QStringList &filterNames) + : QAbstractFileEngineIterator(filters, filterNames) + , done(false) +{ +} + +QFSFileEngineIterator::~QFSFileEngineIterator() +{ +} + +bool QFSFileEngineIterator::hasNext() const +{ + if (!done && !nativeIterator) { + nativeIterator.reset(new QFileSystemIterator(QFileSystemEntry(path()), + filters(), nameFilters())); + advance(); + } + + return !done; +} + +QString QFSFileEngineIterator::next() +{ + if (!hasNext()) + return QString(); + + advance(); + return currentFilePath(); +} + +void QFSFileEngineIterator::advance() const +{ + currentInfo = nextInfo; + + QFileSystemEntry entry; + QFileSystemMetaData data; + if (nativeIterator->advance(entry, data)) { + nextInfo = QFileInfo(new QFileInfoPrivate(entry, data)); + } else { + done = true; + nativeIterator.reset(); + } +} + +QString QFSFileEngineIterator::currentFileName() const +{ + return currentInfo.fileName(); +} + +QFileInfo QFSFileEngineIterator::currentFileInfo() const +{ + return currentInfo; +} + +QT_END_NAMESPACE + +#endif // QT_NO_FSFILEENGINE diff --git a/src/corelib/io/qfsfileengine_iterator_p.h b/src/corelib/io/qfsfileengine_iterator_p.h new file mode 100644 index 0000000000..d4155d6112 --- /dev/null +++ b/src/corelib/io/qfsfileengine_iterator_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFSFILEENGINE_ITERATOR_P_H +#define QFSFILEENGINE_ITERATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qabstractfileengine.h" +#include "qfilesystemiterator_p.h" +#include "qdir.h" + +#ifndef QT_NO_FSFILEENGINE + +QT_BEGIN_NAMESPACE + +class QFSFileEngineIteratorPrivate; +class QFSFileEngineIteratorPlatformSpecificData; + +class QFSFileEngineIterator : public QAbstractFileEngineIterator +{ +public: + QFSFileEngineIterator(QDir::Filters filters, const QStringList &filterNames); + ~QFSFileEngineIterator(); + + QString next(); + bool hasNext() const; + + QString currentFileName() const; + QFileInfo currentFileInfo() const; + +private: + void advance() const; + mutable QScopedPointer<QFileSystemIterator> nativeIterator; + mutable QFileInfo currentInfo; + mutable QFileInfo nextInfo; + mutable bool done; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_FSFILEENGINE + +#endif // QFSFILEENGINE_ITERATOR_P_H diff --git a/src/corelib/io/qfsfileengine_p.h b/src/corelib/io/qfsfileengine_p.h new file mode 100644 index 0000000000..253f461fd1 --- /dev/null +++ b/src/corelib/io/qfsfileengine_p.h @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFSFILEENGINE_P_H +#define QFSFILEENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qplatformdefs.h" +#include "QtCore/qfsfileengine.h" +#include "private/qabstractfileengine_p.h" +#include <QtCore/private/qfilesystementry_p.h> +#include <QtCore/private/qfilesystemmetadata_p.h> +#include <qhash.h> + +#ifdef Q_OS_SYMBIAN +#include <f32file.h> +#endif + +#ifndef QT_NO_FSFILEENGINE + +QT_BEGIN_NAMESPACE + +#if defined(Q_OS_WINCE_STD) && _WIN32_WCE < 0x600 +#define Q_USE_DEPRECATED_MAP_API 1 +#endif + +class Q_AUTOTEST_EXPORT QFSFileEnginePrivate : public QAbstractFileEnginePrivate +{ + Q_DECLARE_PUBLIC(QFSFileEngine) + +public: +#ifdef Q_WS_WIN + static QString longFileName(const QString &path); +#endif + + QFileSystemEntry fileEntry; + QIODevice::OpenMode openMode; + + bool nativeOpen(QIODevice::OpenMode openMode); + bool openFh(QIODevice::OpenMode flags, FILE *fh); + bool openFd(QIODevice::OpenMode flags, int fd); + bool nativeClose(); + bool closeFdFh(); + bool nativeFlush(); + bool flushFh(); + qint64 nativeSize() const; +#ifndef Q_OS_WIN + qint64 sizeFdFh() const; +#endif + qint64 nativePos() const; + qint64 posFdFh() const; + bool nativeSeek(qint64); + bool seekFdFh(qint64); + qint64 nativeRead(char *data, qint64 maxlen); + qint64 readFdFh(char *data, qint64 maxlen); + qint64 nativeReadLine(char *data, qint64 maxlen); + qint64 readLineFdFh(char *data, qint64 maxlen); + qint64 nativeWrite(const char *data, qint64 len); + qint64 writeFdFh(const char *data, qint64 len); + int nativeHandle() const; + bool nativeIsSequential() const; +#ifndef Q_OS_WIN + bool isSequentialFdFh() const; +#endif + + uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags); + bool unmap(uchar *ptr); + + mutable QFileSystemMetaData metaData; + + FILE *fh; +#ifdef Q_OS_SYMBIAN +#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API + RFile64 symbianFile; + TInt64 symbianFilePos; +#else + RFile symbianFile; + + /** + * The cursor position in the underlying file. This differs + * from devicePos because the latter is updated on calls to + * writeData, even if no data was physically transferred to + * the file, but instead stored in the write buffer. + * + * iFilePos is updated on calls to RFile::Read and + * RFile::Write. It is also updated on calls to seek() but + * RFile::Seek is not called when that happens because + * Symbian supports positioned reads and writes, saving a file + * server call, and because Symbian does not support seeking + * past the end of a file. + */ + TInt symbianFilePos; +#endif + mutable int fileHandleForMaps; + int getMapHandle(); +#endif + +#ifdef Q_WS_WIN + HANDLE fileHandle; + HANDLE mapHandle; + QHash<uchar *, DWORD /* offset % AllocationGranularity */> maps; + +#ifndef Q_OS_WINCE + mutable int cachedFd; +#endif + + mutable DWORD fileAttrib; +#else + QHash<uchar *, QPair<int /*offset % PageSize*/, size_t /*length + offset % PageSize*/> > maps; +#endif + int fd; + + enum LastIOCommand + { + IOFlushCommand, + IOReadCommand, + IOWriteCommand + }; + LastIOCommand lastIOCommand; + bool lastFlushFailed; + bool closeFileHandle; + + mutable uint is_sequential : 2; + mutable uint could_stat : 1; + mutable uint tried_stat : 1; +#if !defined(Q_OS_WINCE) + mutable uint need_lstat : 1; + mutable uint is_link : 1; +#endif + +#if defined(Q_OS_WIN) + bool doStat(QFileSystemMetaData::MetaDataFlags flags) const; +#else + bool doStat(QFileSystemMetaData::MetaDataFlags flags = QFileSystemMetaData::PosixStatFlags) const; +#endif + bool isSymlink() const; + +#if defined(Q_OS_WIN32) + int sysOpen(const QString &, int flags); +#endif + +protected: + QFSFileEnginePrivate(); + + void init(); + + QAbstractFileEngine::FileFlags getPermissions(QAbstractFileEngine::FileFlags type) const; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_FSFILEENGINE + +#endif // QFSFILEENGINE_P_H diff --git a/src/corelib/io/qfsfileengine_unix.cpp b/src/corelib/io/qfsfileengine_unix.cpp new file mode 100644 index 0000000000..6c03b32a83 --- /dev/null +++ b/src/corelib/io/qfsfileengine_unix.cpp @@ -0,0 +1,1108 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qabstractfileengine.h" +#include "private/qfsfileengine_p.h" +#include "private/qcore_unix_p.h" +#include "qfilesystementry_p.h" +#include "qfilesystemengine_p.h" + +#ifndef QT_NO_FSFILEENGINE + +#include "qfile.h" +#include "qdir.h" +#include "qdatetime.h" +#include "qvarlengtharray.h" + +#include <sys/mman.h> +#include <stdlib.h> +#include <limits.h> +#if defined(Q_OS_SYMBIAN) +# include <sys/syslimits.h> +# include <f32file.h> +# include <pathinfo.h> +# include "private/qcore_symbian_p.h" +#endif +#include <errno.h> +#if !defined(QWS) && defined(Q_OS_MAC) +# include <private/qcore_mac_p.h> +#endif + +QT_BEGIN_NAMESPACE + +#if defined(Q_OS_SYMBIAN) +/*! + \internal + + Returns true if supplied path is a relative path +*/ +static bool isRelativePathSymbian(const QString& fileName) +{ + return !(fileName.startsWith(QLatin1Char('/')) + || (fileName.length() >= 2 + && ((fileName.at(0).isLetter() && fileName.at(1) == QLatin1Char(':')) + || (fileName.at(0) == QLatin1Char('/') && fileName.at(1) == QLatin1Char('/'))))); +} + +#endif + +#ifndef Q_OS_SYMBIAN +/*! + \internal + + Returns the stdlib open string corresponding to a QIODevice::OpenMode. +*/ +static inline QByteArray openModeToFopenMode(QIODevice::OpenMode flags, const QFileSystemEntry &fileEntry, + QFileSystemMetaData &metaData) +{ + QByteArray mode; + if ((flags & QIODevice::ReadOnly) && !(flags & QIODevice::Truncate)) { + mode = "rb"; + if (flags & QIODevice::WriteOnly) { + metaData.clearFlags(QFileSystemMetaData::FileType); + if (!fileEntry.isEmpty() + && QFileSystemEngine::fillMetaData(fileEntry, metaData, QFileSystemMetaData::FileType) + && metaData.isFile()) { + mode += '+'; + } else { + mode = "wb+"; + } + } + } else if (flags & QIODevice::WriteOnly) { + mode = "wb"; + if (flags & QIODevice::ReadOnly) + mode += '+'; + } + if (flags & QIODevice::Append) { + mode = "ab"; + if (flags & QIODevice::ReadOnly) + mode += '+'; + } + +#if defined(__GLIBC__) && (__GLIBC__ * 0x100 + __GLIBC_MINOR__) >= 0x0207 + // must be glibc >= 2.7 + mode += 'e'; +#endif + + return mode; +} +#endif + +/*! + \internal + + Returns the stdio open flags corresponding to a QIODevice::OpenMode. +*/ +static inline int openModeToOpenFlags(QIODevice::OpenMode mode) +{ + int oflags = QT_OPEN_RDONLY; +#ifdef QT_LARGEFILE_SUPPORT + oflags |= QT_OPEN_LARGEFILE; +#endif + + if ((mode & QFile::ReadWrite) == QFile::ReadWrite) { + oflags = QT_OPEN_RDWR | QT_OPEN_CREAT; + } else if (mode & QFile::WriteOnly) { + oflags = QT_OPEN_WRONLY | QT_OPEN_CREAT; + } + + if (mode & QFile::Append) { + oflags |= QT_OPEN_APPEND; + } else if (mode & QFile::WriteOnly) { + if ((mode & QFile::Truncate) || !(mode & QFile::ReadOnly)) + oflags |= QT_OPEN_TRUNC; + } + + return oflags; +} + +#ifndef Q_OS_SYMBIAN +/*! + \internal + + Sets the file descriptor to close on exec. That is, the file + descriptor is not inherited by child processes. +*/ +static inline bool setCloseOnExec(int fd) +{ + return fd != -1 && fcntl(fd, F_SETFD, FD_CLOEXEC) != -1; +} +#endif + +#ifdef Q_OS_SYMBIAN +/*! + \internal +*/ +bool QFSFileEnginePrivate::nativeOpen(QIODevice::OpenMode openMode) +{ + Q_Q(QFSFileEngine); + + fh = 0; + fd = -1; + + QString fn(QFileSystemEngine::absoluteName(fileEntry).nativeFilePath()); + RFs& fs = qt_s60GetRFs(); + + TUint symbianMode = 0; + + if(openMode & QIODevice::ReadOnly) + symbianMode |= EFileRead; + if(openMode & QIODevice::WriteOnly) + symbianMode |= EFileWrite; + if(openMode & QIODevice::Text) + symbianMode |= EFileStreamText; + + // pre Symbian 9.4, file I/O is always unbuffered, and the enum values don't exist + if(QSysInfo::symbianVersion() >= QSysInfo::SV_9_4) { + if (openMode & QFile::Unbuffered) { + if (openMode & QIODevice::WriteOnly) + symbianMode |= 0x00001000; //EFileWriteDirectIO; + // ### Unbuffered read is not used, because it prevents file open in /resource + // ### and has no obvious benefits + } else { + if (openMode & QIODevice::WriteOnly) + symbianMode |= 0x00000800; //EFileWriteBuffered; + // use implementation defaults for read buffering + } + } + + // Until Qt supports file sharing, we can't support EFileShareReadersOrWriters safely, + // but Qt does this on other platforms and autotests rely on it. + // The reason is that Unix locks are only advisory - the application needs to test the + // lock after opening the file. Symbian and Windows locks are mandatory - opening a + // locked file will fail. + symbianMode |= EFileShareReadersOrWriters; + + TInt r; + //note QIODevice::Truncate only has meaning for read/write access + //write-only files are always truncated unless append is specified + //reference openModeToOpenFlags in qfsfileengine_unix.cpp + if ((openMode & QIODevice::Truncate) || (!(openMode & QIODevice::ReadOnly) && !(openMode & QIODevice::Append))) { + r = symbianFile.Replace(fs, qt_QString2TPtrC(fn), symbianMode); + } else { + r = symbianFile.Open(fs, qt_QString2TPtrC(fn), symbianMode); + if (r == KErrNotFound && (openMode & QIODevice::WriteOnly)) { + r = symbianFile.Create(fs, qt_QString2TPtrC(fn), symbianMode); + } + } + + if (r == KErrNone) { +#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API + TInt64 size; +#else + TInt size; +#endif + r = symbianFile.Size(size); + if (r==KErrNone) { + if (openMode & QIODevice::Append) + symbianFilePos = size; + else + symbianFilePos = 0; + //TODO: port this (QFileSystemMetaData in open?) + //cachedSize = size; + } + } + + if (r != KErrNone) { + q->setError(QFile::OpenError, QSystemError(r, QSystemError::NativeError).toString()); + symbianFile.Close(); + return false; + } + + closeFileHandle = true; + return true; +} + +bool QFSFileEngine::open(QIODevice::OpenMode openMode, const RFile &file, QFile::FileHandleFlags handleFlags) +{ + Q_D(QFSFileEngine); + + // Append implies WriteOnly. + if (openMode & QFile::Append) + openMode |= QFile::WriteOnly; + + // WriteOnly implies Truncate if neither ReadOnly nor Append are sent. + if ((openMode & QFile::WriteOnly) && !(openMode & (QFile::ReadOnly | QFile::Append))) + openMode |= QFile::Truncate; + + d->openMode = openMode; + d->lastFlushFailed = false; + d->closeFileHandle = (handleFlags & QFile::AutoCloseHandle); + d->fileEntry.clear(); + d->fh = 0; + d->fd = -1; + d->tried_stat = 0; + +#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API + //RFile64 adds only functions to RFile, no data members + d->symbianFile = static_cast<const RFile64&>(file); +#else + d->symbianFile = file; +#endif + TInt ret; + d->symbianFilePos = 0; + if (openMode & QFile::Append) { + // Seek to the end when in Append mode. + ret = d->symbianFile.Size(d->symbianFilePos); + } else { + // Seek to current otherwise + ret = d->symbianFile.Seek(ESeekCurrent, d->symbianFilePos); + } + + if (ret != KErrNone) { + setError(QFile::OpenError, QSystemError(ret, QSystemError::NativeError).toString()); + + d->openMode = QIODevice::NotOpen; +#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API + d->symbianFile = RFile64(); +#else + d->symbianFile = RFile(); +#endif + return false; + } + + // Extract filename (best effort) + TFileName fn; + TInt err = d->symbianFile.FullName(fn); + if (err == KErrNone) + d->fileEntry = QFileSystemEntry(qt_TDesC2QString(fn), QFileSystemEntry::FromNativePath()); + else + d->fileEntry.clear(); + + return true; +} +#else +/*! + \internal +*/ +bool QFSFileEnginePrivate::nativeOpen(QIODevice::OpenMode openMode) +{ + Q_Q(QFSFileEngine); + + if (openMode & QIODevice::Unbuffered) { + int flags = openModeToOpenFlags(openMode); + + // Try to open the file in unbuffered mode. + do { + fd = QT_OPEN(fileEntry.nativeFilePath().constData(), flags, 0666); + } while (fd == -1 && errno == EINTR); + + // On failure, return and report the error. + if (fd == -1) { + q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, + qt_error_string(errno)); + return false; + } + + if (!(openMode & QIODevice::WriteOnly)) { + // we don't need this check if we tried to open for writing because then + // we had received EISDIR anyway. + if (QFileSystemEngine::fillMetaData(fd, metaData) + && metaData.isDirectory()) { + q->setError(QFile::OpenError, QLatin1String("file to open is a directory")); + QT_CLOSE(fd); + return false; + } + } + + // Seek to the end when in Append mode. + if (flags & QFile::Append) { + int ret; + do { + ret = QT_LSEEK(fd, 0, SEEK_END); + } while (ret == -1 && errno == EINTR); + + if (ret == -1) { + q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, + qt_error_string(int(errno))); + return false; + } + } + + fh = 0; + } else { + QByteArray fopenMode = openModeToFopenMode(openMode, fileEntry, metaData); + + // Try to open the file in buffered mode. + do { + fh = QT_FOPEN(fileEntry.nativeFilePath().constData(), fopenMode.constData()); + } while (!fh && errno == EINTR); + + // On failure, return and report the error. + if (!fh) { + q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, + qt_error_string(int(errno))); + return false; + } + + if (!(openMode & QIODevice::WriteOnly)) { + // we don't need this check if we tried to open for writing because then + // we had received EISDIR anyway. + if (QFileSystemEngine::fillMetaData(QT_FILENO(fh), metaData) + && metaData.isDirectory()) { + q->setError(QFile::OpenError, QLatin1String("file to open is a directory")); + fclose(fh); + return false; + } + } + + setCloseOnExec(fileno(fh)); // ignore failure + + // Seek to the end when in Append mode. + if (openMode & QIODevice::Append) { + int ret; + do { + ret = QT_FSEEK(fh, 0, SEEK_END); + } while (ret == -1 && errno == EINTR); + + if (ret == -1) { + q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, + qt_error_string(int(errno))); + return false; + } + } + + fd = -1; + } + + closeFileHandle = true; + return true; +} +#endif + +/*! + \internal +*/ +bool QFSFileEnginePrivate::nativeClose() +{ + return closeFdFh(); +} + +/*! + \internal + +*/ +bool QFSFileEnginePrivate::nativeFlush() +{ +#ifdef Q_OS_SYMBIAN + if (symbianFile.SubSessionHandle()) + return (KErrNone == symbianFile.Flush()); +#endif + return fh ? flushFh() : fd != -1; +} + +/*! + \internal +*/ +qint64 QFSFileEnginePrivate::nativeRead(char *data, qint64 len) +{ + Q_Q(QFSFileEngine); + +#ifdef Q_OS_SYMBIAN + if (symbianFile.SubSessionHandle()) { + if(len > KMaxTInt) { + //this check is more likely to catch a corrupt length, since it isn't possible to allocate 2GB buffers (yet..) + q->setError(QFile::ReadError, QLatin1String("Maximum 2GB in single read on this platform")); + return -1; + } + TPtr8 ptr(reinterpret_cast<TUint8*>(data), static_cast<TInt>(len)); + TInt r = symbianFile.Read(symbianFilePos, ptr); + if (r != KErrNone) + { + q->setError(QFile::ReadError, QSystemError(r, QSystemError::NativeError).toString()); + return -1; + } + symbianFilePos += ptr.Length(); + return qint64(ptr.Length()); + } +#endif + if (fh && nativeIsSequential()) { + size_t readBytes = 0; + int oldFlags = fcntl(QT_FILENO(fh), F_GETFL); + for (int i = 0; i < 2; ++i) { + // Unix: Make the underlying file descriptor non-blocking + if ((oldFlags & O_NONBLOCK) == 0) + fcntl(QT_FILENO(fh), F_SETFL, oldFlags | O_NONBLOCK); + + // Cross platform stdlib read + size_t read = 0; + do { + read = fread(data + readBytes, 1, size_t(len - readBytes), fh); + } while (read == 0 && !feof(fh) && errno == EINTR); + if (read > 0) { + readBytes += read; + break; + } else { + if (readBytes) + break; + readBytes = read; + } + + // Unix: Restore the blocking state of the underlying socket + if ((oldFlags & O_NONBLOCK) == 0) { + fcntl(QT_FILENO(fh), F_SETFL, oldFlags); + if (readBytes == 0) { + int readByte = 0; + do { + readByte = fgetc(fh); + } while (readByte == -1 && errno == EINTR); + if (readByte != -1) { + *data = uchar(readByte); + readBytes += 1; + } else { + break; + } + } + } + } + // Unix: Restore the blocking state of the underlying socket + if ((oldFlags & O_NONBLOCK) == 0) { + fcntl(QT_FILENO(fh), F_SETFL, oldFlags); + } + if (readBytes == 0 && !feof(fh)) { + // if we didn't read anything and we're not at EOF, it must be an error + q->setError(QFile::ReadError, qt_error_string(int(errno))); + return -1; + } + return readBytes; + } + + return readFdFh(data, len); +} + +/*! + \internal +*/ +qint64 QFSFileEnginePrivate::nativeReadLine(char *data, qint64 maxlen) +{ + return readLineFdFh(data, maxlen); +} + +/*! + \internal +*/ +qint64 QFSFileEnginePrivate::nativeWrite(const char *data, qint64 len) +{ +#ifdef Q_OS_SYMBIAN + Q_Q(QFSFileEngine); + if (symbianFile.SubSessionHandle()) { + if(len > KMaxTInt) { + //this check is more likely to catch a corrupt length, since it isn't possible to allocate 2GB buffers (yet..) + q->setError(QFile::WriteError, QLatin1String("Maximum 2GB in single write on this platform")); + return -1; + } + const TPtrC8 ptr(reinterpret_cast<const TUint8*>(data), static_cast<TInt>(len)); +#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API + TInt64 eofpos = 0; +#else + TInt eofpos = 0; +#endif + //The end of file position is not cached because QFile is read/write sharable, therefore another + //process may have altered the file size. + TInt r = symbianFile.Seek(ESeekEnd, eofpos); + if (r == KErrNone && symbianFilePos > eofpos) { + //seek position is beyond end of file so file needs to be extended before write. + //note that SetSize does not zero-initialise (c.f. posix lseek) + r = symbianFile.SetSize(symbianFilePos); + } + if (r == KErrNone) { + //write to specific position in the file (i.e. use our own cursor rather than calling seek) + r = symbianFile.Write(symbianFilePos, ptr); + } + if (r != KErrNone) { + q->setError(QFile::WriteError, QSystemError(r, QSystemError::NativeError).toString()); + return -1; + } + symbianFilePos += len; + return len; + } +#endif + return writeFdFh(data, len); +} + +/*! + \internal +*/ +qint64 QFSFileEnginePrivate::nativePos() const +{ +#ifdef Q_OS_SYMBIAN + const Q_Q(QFSFileEngine); + if (symbianFile.SubSessionHandle()) { + return symbianFilePos; + } +#endif + return posFdFh(); +} + +/*! + \internal +*/ +bool QFSFileEnginePrivate::nativeSeek(qint64 pos) +{ +#ifdef Q_OS_SYMBIAN + Q_Q(QFSFileEngine); + if (symbianFile.SubSessionHandle()) { +#ifndef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API + if(pos > KMaxTInt) { + q->setError(QFile::PositionError, QLatin1String("Maximum 2GB file position on this platform")); + return false; + } +#endif + symbianFilePos = pos; + return true; + } +#endif + return seekFdFh(pos); +} + +/*! + \internal +*/ +int QFSFileEnginePrivate::nativeHandle() const +{ + return fh ? fileno(fh) : fd; +} + +#ifdef Q_OS_SYMBIAN +int QFSFileEnginePrivate::getMapHandle() +{ + if (symbianFile.SubSessionHandle()) { + // Symbian file handle can't be used for open C mmap() so open the file with open C as well. + if (fileHandleForMaps < 0) { + int flags = openModeToOpenFlags(openMode); + flags &= ~(O_CREAT | O_TRUNC); + fileHandleForMaps = ::wopen((wchar_t*)(fileEntry.nativeFilePath().utf16()), flags, 0666); + } + return fileHandleForMaps; + } + return nativeHandle(); +} +#endif + +/*! + \internal +*/ +bool QFSFileEnginePrivate::nativeIsSequential() const +{ +#ifdef Q_OS_SYMBIAN + if (symbianFile.SubSessionHandle()) + return false; +#endif + return isSequentialFdFh(); +} + +bool QFSFileEngine::remove() +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::removeFile(d->fileEntry, error); + d->metaData.clear(); + if (!ret) { + setError(QFile::RemoveError, error.toString()); + } + return ret; +} + +bool QFSFileEngine::copy(const QString &newName) +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::copyFile(d->fileEntry, QFileSystemEntry(newName), error); + if (!ret) { + setError(QFile::CopyError, error.toString()); + } + return ret; +} + +bool QFSFileEngine::rename(const QString &newName) +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::renameFile(d->fileEntry, QFileSystemEntry(newName), error); + + if (!ret) { + setError(QFile::RenameError, error.toString()); + } + + return ret; +} + +bool QFSFileEngine::link(const QString &newName) +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::createLink(d->fileEntry, QFileSystemEntry(newName), error); + if (!ret) { + setError(QFile::RenameError, error.toString()); + } + return ret; +} + +qint64 QFSFileEnginePrivate::nativeSize() const +{ +#ifdef Q_OS_SYMBIAN + const Q_Q(QFSFileEngine); + if (symbianFile.SubSessionHandle()) { +#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API + qint64 size; +#else + TInt size; +#endif + TInt err = symbianFile.Size(size); + if(err != KErrNone) { + const_cast<QFSFileEngine*>(q)->setError(QFile::PositionError, QSystemError(err, QSystemError::NativeError).toString()); + return 0; + } + return size; + } +#endif + return sizeFdFh(); +} + +bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const +{ + return QFileSystemEngine::createDirectory(QFileSystemEntry(name), createParentDirectories); +} + +bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const +{ + return QFileSystemEngine::removeDirectory(QFileSystemEntry(name), recurseParentDirectories); +} + +bool QFSFileEngine::caseSensitive() const +{ +#if defined(Q_OS_SYMBIAN) + return false; +#else + return true; +#endif +} + +bool QFSFileEngine::setCurrentPath(const QString &path) +{ + return QFileSystemEngine::setCurrentPath(QFileSystemEntry(path)); +} + +QString QFSFileEngine::currentPath(const QString &) +{ + return QFileSystemEngine::currentPath().filePath(); +} + +QString QFSFileEngine::homePath() +{ + return QFileSystemEngine::homePath(); +} + +QString QFSFileEngine::rootPath() +{ + return QFileSystemEngine::rootPath(); +} + +QString QFSFileEngine::tempPath() +{ + return QFileSystemEngine::tempPath(); +} + +QFileInfoList QFSFileEngine::drives() +{ + QFileInfoList ret; +#if defined(Q_OS_SYMBIAN) + TDriveList driveList; + RFs rfs = qt_s60GetRFs(); + TInt err = rfs.DriveList(driveList); + if (err == KErrNone) { + char driveName[] = "A:/"; + + for (char i = 0; i < KMaxDrives; i++) { + if (driveList[i]) { + driveName[0] = 'A' + i; + ret.append(QFileInfo(QLatin1String(driveName))); + } + } + } else { + qWarning("QFSFileEngine::drives: Getting drives failed"); + } +#else + ret.append(QFileInfo(rootPath())); +#endif + return ret; +} + +bool QFSFileEnginePrivate::doStat(QFileSystemMetaData::MetaDataFlags flags) const +{ + if (!tried_stat || !metaData.hasFlags(flags)) { + tried_stat = 1; + + int localFd = fd; + if (fh && fileEntry.isEmpty()) + localFd = QT_FILENO(fh); + if (localFd != -1) + QFileSystemEngine::fillMetaData(localFd, metaData); + + if (metaData.missingFlags(flags) && !fileEntry.isEmpty()) + QFileSystemEngine::fillMetaData(fileEntry, metaData, metaData.missingFlags(flags)); + } + + return metaData.exists(); +} + +bool QFSFileEnginePrivate::isSymlink() const +{ + if (!metaData.hasFlags(QFileSystemMetaData::LinkType)) + QFileSystemEngine::fillMetaData(fileEntry, metaData, QFileSystemMetaData::LinkType); + + return metaData.isLink(); +} + +/*! + \reimp +*/ +QAbstractFileEngine::FileFlags QFSFileEngine::fileFlags(FileFlags type) const +{ + Q_D(const QFSFileEngine); + + if (type & Refresh) + d->metaData.clear(); + + QAbstractFileEngine::FileFlags ret = 0; + + if (type & FlagsMask) + ret |= LocalDiskFlag; + + bool exists; + { + QFileSystemMetaData::MetaDataFlags queryFlags = 0; + + queryFlags |= QFileSystemMetaData::MetaDataFlags(uint(type)) + & QFileSystemMetaData::Permissions; + + if (type & TypesMask) + queryFlags |= QFileSystemMetaData::AliasType + | QFileSystemMetaData::LinkType + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::BundleType; + + if (type & FlagsMask) + queryFlags |= QFileSystemMetaData::HiddenAttribute + | QFileSystemMetaData::ExistsAttribute; + + queryFlags |= QFileSystemMetaData::LinkType; + + exists = d->doStat(queryFlags); + } + + if (!exists && !d->metaData.isLink()) + return ret; + + if (exists && (type & PermsMask)) + ret |= FileFlags(uint(d->metaData.permissions())); + + if (type & TypesMask) { + if (d->metaData.isAlias()) { + ret |= LinkType; + } else { + if ((type & LinkType) && d->metaData.isLink()) + ret |= LinkType; + if (exists) { + if (d->metaData.isFile()) { + ret |= FileType; + } else if (d->metaData.isDirectory()) { + ret |= DirectoryType; + if ((type & BundleType) && d->metaData.isBundle()) + ret |= BundleType; + } + } + } + } + + if (type & FlagsMask) { + if (exists) + ret |= ExistsFlag; + if (d->fileEntry.isRoot()) + ret |= RootFlag; + else if (d->metaData.isHidden()) + ret |= HiddenFlag; + } + + return ret; +} + +QString QFSFileEngine::fileName(FileName file) const +{ + Q_D(const QFSFileEngine); + if (file == BundleName) { + return QFileSystemEngine::bundleName(d->fileEntry); + } else if (file == BaseName) { + return d->fileEntry.fileName(); + } else if (file == PathName) { + return d->fileEntry.path(); + } else if (file == AbsoluteName || file == AbsolutePathName) { + QFileSystemEntry entry(QFileSystemEngine::absoluteName(d->fileEntry)); + if (file == AbsolutePathName) { + return entry.path(); + } + return entry.filePath(); + } else if (file == CanonicalName || file == CanonicalPathName) { + QFileSystemEntry entry(QFileSystemEngine::canonicalName(d->fileEntry, d->metaData)); + if (file == CanonicalPathName) + return entry.path(); + return entry.filePath(); + } else if (file == LinkName) { + if (d->isSymlink()) { + QFileSystemEntry entry = QFileSystemEngine::getLinkTarget(d->fileEntry, d->metaData); + return entry.filePath(); + } + return QString(); + } + return d->fileEntry.filePath(); +} + +bool QFSFileEngine::isRelativePath() const +{ + Q_D(const QFSFileEngine); +#if defined(Q_OS_SYMBIAN) + return isRelativePathSymbian(d->fileEntry.filePath()); +#else + return d->fileEntry.filePath().length() ? d->fileEntry.filePath()[0] != QLatin1Char('/') : true; +#endif +} + +uint QFSFileEngine::ownerId(FileOwner own) const +{ + Q_D(const QFSFileEngine); + static const uint nobodyID = (uint) -2; + + if (d->doStat(QFileSystemMetaData::OwnerIds)) + return d->metaData.ownerId(own); + + return nobodyID; +} + +QString QFSFileEngine::owner(FileOwner own) const +{ +#ifndef Q_OS_SYMBIAN + if (own == OwnerUser) + return QFileSystemEngine::resolveUserName(ownerId(own)); + return QFileSystemEngine::resolveGroupName(ownerId(own)); +#else + return QString(); +#endif +} + +bool QFSFileEngine::setPermissions(uint perms) +{ + Q_D(QFSFileEngine); + QSystemError error; + if (!QFileSystemEngine::setPermissions(d->fileEntry, QFile::Permissions(perms), error, 0)) { + setError(QFile::PermissionsError, error.toString()); + return false; + } + return true; +} + +#ifdef Q_OS_SYMBIAN +bool QFSFileEngine::setSize(qint64 size) +{ + Q_D(QFSFileEngine); + bool ret = false; + TInt err = KErrNone; + if (d->symbianFile.SubSessionHandle()) { + TInt err = d->symbianFile.SetSize(size); + ret = (err == KErrNone); + if (ret && d->symbianFilePos > size) + d->symbianFilePos = size; + } + else if (d->fd != -1) + ret = QT_FTRUNCATE(d->fd, size) == 0; + else if (d->fh) + ret = QT_FTRUNCATE(QT_FILENO(d->fh), size) == 0; + else { + RFile tmp; + QString symbianFilename(d->fileEntry.nativeFilePath()); + err = tmp.Open(qt_s60GetRFs(), qt_QString2TPtrC(symbianFilename), EFileWrite); + if (err == KErrNone) + { + err = tmp.SetSize(size); + tmp.Close(); + } + ret = (err == KErrNone); + } + if (!ret) { + QSystemError error; + if (err) + error = QSystemError(err, QSystemError::NativeError); + else + error = QSystemError(errno, QSystemError::StandardLibraryError); + setError(QFile::ResizeError, error.toString()); + } + return ret; +} +#else +bool QFSFileEngine::setSize(qint64 size) +{ + Q_D(QFSFileEngine); + bool ret = false; + if (d->fd != -1) + ret = QT_FTRUNCATE(d->fd, size) == 0; + else if (d->fh) + ret = QT_FTRUNCATE(QT_FILENO(d->fh), size) == 0; + else + ret = QT_TRUNCATE(d->fileEntry.nativeFilePath().constData(), size) == 0; + if (!ret) + setError(QFile::ResizeError, qt_error_string(errno)); + return ret; +} +#endif + +QDateTime QFSFileEngine::fileTime(FileTime time) const +{ + Q_D(const QFSFileEngine); + + if (d->doStat(QFileSystemMetaData::Times)) + return d->metaData.fileTime(time); + + return QDateTime(); +} + +uchar *QFSFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags) +{ + Q_Q(QFSFileEngine); + Q_UNUSED(flags); + if (openMode == QIODevice::NotOpen) { + q->setError(QFile::PermissionsError, qt_error_string(int(EACCES))); + return 0; + } + + if (offset < 0 || offset != qint64(QT_OFF_T(offset)) + || size < 0 || quint64(size) > quint64(size_t(-1))) { + q->setError(QFile::UnspecifiedError, qt_error_string(int(EINVAL))); + return 0; + } + + // If we know the mapping will extend beyond EOF, fail early to avoid + // undefined behavior. Otherwise, let mmap have its say. + if (doStat(QFileSystemMetaData::SizeAttribute) + && (QT_OFF_T(size) > metaData.size() - QT_OFF_T(offset))) + qWarning("QFSFileEngine::map: Mapping a file beyond its size is not portable"); + + int access = 0; + if (openMode & QIODevice::ReadOnly) access |= PROT_READ; + if (openMode & QIODevice::WriteOnly) access |= PROT_WRITE; + +#if defined(Q_OS_INTEGRITY) + int pageSize = sysconf(_SC_PAGESIZE); +#else + int pageSize = getpagesize(); +#endif + int extra = offset % pageSize; + + if (quint64(size + extra) > quint64((size_t)-1)) { + q->setError(QFile::UnspecifiedError, qt_error_string(int(EINVAL))); + return 0; + } + + size_t realSize = (size_t)size + extra; + QT_OFF_T realOffset = QT_OFF_T(offset); + realOffset &= ~(QT_OFF_T(pageSize - 1)); + +#ifdef Q_OS_SYMBIAN + void *mapAddress; + TRAPD(err, mapAddress = QT_MMAP((void*)0, realSize, + access, MAP_SHARED, getMapHandle(), realOffset)); + if (err != KErrNone) { + qWarning("OpenC bug: leave from mmap %d", err); + mapAddress = MAP_FAILED; + errno = EINVAL; + } +#else + void *mapAddress = QT_MMAP((void*)0, realSize, + access, MAP_SHARED, nativeHandle(), realOffset); +#endif + if (MAP_FAILED != mapAddress) { + uchar *address = extra + static_cast<uchar*>(mapAddress); + maps[address] = QPair<int,size_t>(extra, realSize); + return address; + } + + switch(errno) { + case EBADF: + q->setError(QFile::PermissionsError, qt_error_string(int(EACCES))); + break; + case ENFILE: + case ENOMEM: + q->setError(QFile::ResourceError, qt_error_string(int(errno))); + break; + case EINVAL: + // size are out of bounds + default: + q->setError(QFile::UnspecifiedError, qt_error_string(int(errno))); + break; + } + return 0; +} + +bool QFSFileEnginePrivate::unmap(uchar *ptr) +{ +#if !defined(Q_OS_INTEGRITY) + Q_Q(QFSFileEngine); + if (!maps.contains(ptr)) { + q->setError(QFile::PermissionsError, qt_error_string(EACCES)); + return false; + } + + uchar *start = ptr - maps[ptr].first; + size_t len = maps[ptr].second; + if (-1 == munmap(start, len)) { + q->setError(QFile::UnspecifiedError, qt_error_string(errno)); + return false; + } + maps.remove(ptr); + return true; +#else + return false; +#endif +} + +QT_END_NAMESPACE + +#endif // QT_NO_FSFILEENGINE diff --git a/src/corelib/io/qfsfileengine_win.cpp b/src/corelib/io/qfsfileengine_win.cpp new file mode 100644 index 0000000000..9c858c2e1d --- /dev/null +++ b/src/corelib/io/qfsfileengine_win.cpp @@ -0,0 +1,1008 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#define _POSIX_ +#include "qplatformdefs.h" +#include "qabstractfileengine.h" +#include "private/qfsfileengine_p.h" +#include "qfilesystemengine_p.h" +#include <qdebug.h> + +#include "qfile.h" +#include "qdir.h" +#include "private/qmutexpool_p.h" +#include "qvarlengtharray.h" +#include "qdatetime.h" +#include "qt_windows.h" + +#if !defined(Q_OS_WINCE) +# include <sys/types.h> +# include <direct.h> +# include <winioctl.h> +#else +# include <types.h> +#endif +#include <objbase.h> +#include <shlobj.h> +#include <initguid.h> +#include <accctrl.h> +#include <ctype.h> +#include <limits.h> +#define SECURITY_WIN32 +#include <security.h> + +#ifndef PATH_MAX +#define PATH_MAX FILENAME_MAX +#endif + +QT_BEGIN_NAMESPACE + +#if !defined(Q_OS_WINCE) +static inline bool isUncPath(const QString &path) +{ + // Starts with \\, but not \\. + return (path.startsWith(QLatin1String("\\\\")) + && path.size() > 2 && path.at(2) != QLatin1Char('.')); +} +#endif + +/*! + \internal +*/ +QString QFSFileEnginePrivate::longFileName(const QString &path) +{ + if (path.startsWith(QLatin1String("\\\\.\\"))) + return path; + + QString absPath = QFileSystemEngine::nativeAbsoluteFilePath(path); +#if !defined(Q_OS_WINCE) + QString prefix = QLatin1String("\\\\?\\"); + if (isUncPath(absPath)) { + prefix.append(QLatin1String("UNC\\")); // "\\\\?\\UNC\\" + absPath.remove(0, 2); + } + return prefix + absPath; +#else + return absPath; +#endif +} + +/* + \internal +*/ +bool QFSFileEnginePrivate::nativeOpen(QIODevice::OpenMode openMode) +{ + Q_Q(QFSFileEngine); + + // All files are opened in share mode (both read and write). + DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + int accessRights = 0; + if (openMode & QIODevice::ReadOnly) + accessRights |= GENERIC_READ; + if (openMode & QIODevice::WriteOnly) + accessRights |= GENERIC_WRITE; + + SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE }; + + // WriteOnly can create files, ReadOnly cannot. + DWORD creationDisp = (openMode & QIODevice::WriteOnly) ? OPEN_ALWAYS : OPEN_EXISTING; + // Create the file handle. + fileHandle = CreateFile((const wchar_t*)fileEntry.nativeFilePath().utf16(), + accessRights, + shareMode, + &securityAtts, + creationDisp, + FILE_ATTRIBUTE_NORMAL, + NULL); + + // Bail out on error. + if (fileHandle == INVALID_HANDLE_VALUE) { + q->setError(QFile::OpenError, qt_error_string()); + return false; + } + + // Truncate the file after successfully opening it if Truncate is passed. + if (openMode & QIODevice::Truncate) + q->setSize(0); + + return true; +} + +/* + \internal +*/ +bool QFSFileEnginePrivate::nativeClose() +{ + Q_Q(QFSFileEngine); + if (fh || fd != -1) { + // stdlib / stdio mode. + return closeFdFh(); + } + + // Windows native mode. + bool ok = true; + +#ifndef Q_OS_WINCE + if (cachedFd != -1) { + if (::_close(cachedFd) && !::CloseHandle(fileHandle)) { + q->setError(QFile::UnspecifiedError, qt_error_string()); + ok = false; + } + + // System handle is closed with associated file descriptor. + fileHandle = INVALID_HANDLE_VALUE; + cachedFd = -1; + + return ok; + } +#endif + + if ((fileHandle == INVALID_HANDLE_VALUE || !::CloseHandle(fileHandle))) { + q->setError(QFile::UnspecifiedError, qt_error_string()); + ok = false; + } + fileHandle = INVALID_HANDLE_VALUE; + return ok; +} + +/* + \internal +*/ +bool QFSFileEnginePrivate::nativeFlush() +{ + if (fh) { + // Buffered stdlib mode. + return flushFh(); + } + if (fd != -1) { + // Unbuffered stdio mode; always succeeds (no buffer). + return true; + } + + // Windows native mode; flushing is + // unnecessary. FlushFileBuffers(), the equivalent of sync() or + // fsync() on Unix, does a low-level flush to the disk, and we + // don't expose an API for this. + return true; +} + +/* + \internal +*/ +qint64 QFSFileEnginePrivate::nativeSize() const +{ + Q_Q(const QFSFileEngine); + QFSFileEngine *thatQ = const_cast<QFSFileEngine *>(q); + + // ### Don't flush; for buffered files, we should get away with ftell. + thatQ->flush(); + + // Always retrive the current information + metaData.clearFlags(QFileSystemMetaData::SizeAttribute); +#if defined(Q_OS_WINCE) + // Buffered stdlib mode. + if (fh) { + QT_OFF_T oldPos = QT_FTELL(fh); + QT_FSEEK(fh, 0, SEEK_END); + qint64 fileSize = (qint64)QT_FTELL(fh); + QT_FSEEK(fh, oldPos, SEEK_SET); + if (fileSize == -1) { + fileSize = 0; + thatQ->setError(QFile::UnspecifiedError, qt_error_string(errno)); + } + return fileSize; + } + if (fd != -1) { + thatQ->setError(QFile::UnspecifiedError, QLatin1String("Not implemented!")); + return 0; + } +#endif + bool filled = false; + if (fileHandle != INVALID_HANDLE_VALUE && openMode != QIODevice::NotOpen ) + filled = QFileSystemEngine::fillMetaData(fileHandle, metaData, + QFileSystemMetaData::SizeAttribute); + else + filled = doStat(QFileSystemMetaData::SizeAttribute); + + if (!filled) { + thatQ->setError(QFile::UnspecifiedError, qt_error_string(errno)); + } + return metaData.size(); +} + +/* + \internal +*/ +qint64 QFSFileEnginePrivate::nativePos() const +{ + Q_Q(const QFSFileEngine); + QFSFileEngine *thatQ = const_cast<QFSFileEngine *>(q); + + if (fh || fd != -1) { + // stdlib / stido mode. + return posFdFh(); + } + + // Windows native mode. + if (fileHandle == INVALID_HANDLE_VALUE) + return 0; + +#if !defined(Q_OS_WINCE) + LARGE_INTEGER currentFilePos; + LARGE_INTEGER offset; + offset.QuadPart = 0; + if (!::SetFilePointerEx(fileHandle, offset, ¤tFilePos, FILE_CURRENT)) { + thatQ->setError(QFile::UnspecifiedError, qt_error_string()); + return 0; + } + + return qint64(currentFilePos.QuadPart); +#else + LARGE_INTEGER filepos; + filepos.HighPart = 0; + DWORD newFilePointer = SetFilePointer(fileHandle, 0, &filepos.HighPart, FILE_CURRENT); + if (newFilePointer == 0xFFFFFFFF && GetLastError() != NO_ERROR) { + thatQ->setError(QFile::UnspecifiedError, qt_error_string()); + return 0; + } + + filepos.LowPart = newFilePointer; + return filepos.QuadPart; +#endif +} + +/* + \internal +*/ +bool QFSFileEnginePrivate::nativeSeek(qint64 pos) +{ + Q_Q(QFSFileEngine); + + if (fh || fd != -1) { + // stdlib / stdio mode. + return seekFdFh(pos); + } + +#if !defined(Q_OS_WINCE) + LARGE_INTEGER currentFilePos; + LARGE_INTEGER offset; + offset.QuadPart = pos; + if (!::SetFilePointerEx(fileHandle, offset, ¤tFilePos, FILE_BEGIN)) { + q->setError(QFile::UnspecifiedError, qt_error_string()); + return false; + } + + return true; +#else + DWORD newFilePointer; + LARGE_INTEGER *li = reinterpret_cast<LARGE_INTEGER*>(&pos); + newFilePointer = SetFilePointer(fileHandle, li->LowPart, &li->HighPart, FILE_BEGIN); + if (newFilePointer == 0xFFFFFFFF && GetLastError() != NO_ERROR) { + q->setError(QFile::PositionError, qt_error_string()); + return false; + } + + return true; +#endif +} + +/* + \internal +*/ +qint64 QFSFileEnginePrivate::nativeRead(char *data, qint64 maxlen) +{ + Q_Q(QFSFileEngine); + + if (fh || fd != -1) { + // stdio / stdlib mode. + if (fh && nativeIsSequential() && feof(fh)) { + q->setError(QFile::ReadError, qt_error_string(int(errno))); + return -1; + } + + return readFdFh(data, maxlen); + } + + // Windows native mode. + if (fileHandle == INVALID_HANDLE_VALUE) + return -1; + + DWORD bytesToRead = DWORD(maxlen); // <- lossy + + // Reading on Windows fails with ERROR_NO_SYSTEM_RESOURCES when + // the chunks are too large, so we limit the block size to 32MB. + static const DWORD maxBlockSize = 32 * 1024 * 1024; + + qint64 totalRead = 0; + do { + DWORD blockSize = qMin<DWORD>(bytesToRead, maxBlockSize); + DWORD bytesRead; + if (!ReadFile(fileHandle, data + totalRead, blockSize, &bytesRead, NULL)) { + if (totalRead == 0) { + // Note: only return failure if the first ReadFile fails. + q->setError(QFile::ReadError, qt_error_string()); + return -1; + } + break; + } + if (bytesRead == 0) + break; + totalRead += bytesRead; + bytesToRead -= bytesRead; + } while (totalRead < maxlen); + return qint64(totalRead); +} + +/* + \internal +*/ +qint64 QFSFileEnginePrivate::nativeReadLine(char *data, qint64 maxlen) +{ + Q_Q(QFSFileEngine); + + if (fh || fd != -1) { + // stdio / stdlib mode. + return readLineFdFh(data, maxlen); + } + + // Windows native mode. + if (fileHandle == INVALID_HANDLE_VALUE) + return -1; + + // ### No equivalent in Win32? + return q->QAbstractFileEngine::readLine(data, maxlen); +} + +/* + \internal +*/ +qint64 QFSFileEnginePrivate::nativeWrite(const char *data, qint64 len) +{ + Q_Q(QFSFileEngine); + + if (fh || fd != -1) { + // stdio / stdlib mode. + return writeFdFh(data, len); + } + + // Windows native mode. + if (fileHandle == INVALID_HANDLE_VALUE) + return -1; + + qint64 bytesToWrite = DWORD(len); // <- lossy + + // Writing on Windows fails with ERROR_NO_SYSTEM_RESOURCES when + // the chunks are too large, so we limit the block size to 32MB. + static const DWORD maxBlockSize = 32 * 1024 * 1024; + + qint64 totalWritten = 0; + do { + DWORD blockSize = qMin<DWORD>(bytesToWrite, maxBlockSize); + DWORD bytesWritten; + if (!WriteFile(fileHandle, data + totalWritten, blockSize, &bytesWritten, NULL)) { + if (totalWritten == 0) { + // Note: Only return error if the first WriteFile failed. + q->setError(QFile::WriteError, qt_error_string()); + return -1; + } + break; + } + if (bytesWritten == 0) + break; + totalWritten += bytesWritten; + bytesToWrite -= bytesWritten; + } while (totalWritten < len); + return qint64(totalWritten); +} + +/* + \internal +*/ +int QFSFileEnginePrivate::nativeHandle() const +{ + if (fh || fd != -1) + return fh ? QT_FILENO(fh) : fd; +#ifndef Q_OS_WINCE + if (cachedFd != -1) + return cachedFd; + + int flags = 0; + if (openMode & QIODevice::Append) + flags |= _O_APPEND; + if (!(openMode & QIODevice::WriteOnly)) + flags |= _O_RDONLY; + cachedFd = _open_osfhandle((intptr_t) fileHandle, flags); + return cachedFd; +#else + return -1; +#endif +} + +/* + \internal +*/ +bool QFSFileEnginePrivate::nativeIsSequential() const +{ +#if !defined(Q_OS_WINCE) + HANDLE handle = fileHandle; + if (fh || fd != -1) + handle = (HANDLE)_get_osfhandle(fh ? QT_FILENO(fh) : fd); + if (handle == INVALID_HANDLE_VALUE) + return false; + + DWORD fileType = GetFileType(handle); + return (fileType == FILE_TYPE_CHAR) + || (fileType == FILE_TYPE_PIPE); +#else + return false; +#endif +} + +bool QFSFileEngine::remove() +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::removeFile(d->fileEntry, error); + if (!ret) + setError(QFile::RemoveError, error.toString()); + return ret; +} + +bool QFSFileEngine::copy(const QString ©Name) +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::copyFile(d->fileEntry, QFileSystemEntry(copyName), error); + if (!ret) + setError(QFile::CopyError, error.toString()); + return ret; +} + +bool QFSFileEngine::rename(const QString &newName) +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::renameFile(d->fileEntry, QFileSystemEntry(newName), error); + if (!ret) + setError(QFile::RenameError, error.toString()); + return ret; +} + +bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const +{ + return QFileSystemEngine::createDirectory(QFileSystemEntry(name), createParentDirectories); +} + +bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const +{ + return QFileSystemEngine::removeDirectory(QFileSystemEntry(name), recurseParentDirectories); +} + +bool QFSFileEngine::caseSensitive() const +{ + return false; +} + +bool QFSFileEngine::setCurrentPath(const QString &path) +{ + return QFileSystemEngine::setCurrentPath(QFileSystemEntry(path)); +} + +QString QFSFileEngine::currentPath(const QString &fileName) +{ +#if !defined(Q_OS_WINCE) + QString ret; + //if filename is a drive: then get the pwd of that drive + if (fileName.length() >= 2 && + fileName.at(0).isLetter() && fileName.at(1) == QLatin1Char(':')) { + int drv = fileName.toUpper().at(0).toLatin1() - 'A' + 1; + if (_getdrive() != drv) { + wchar_t buf[PATH_MAX]; + ::_wgetdcwd(drv, buf, PATH_MAX); + ret = QString::fromWCharArray(buf); + } + } + if (ret.isEmpty()) { + //just the pwd + ret = QFileSystemEngine::currentPath().filePath(); + } + if (ret.length() >= 2 && ret[1] == QLatin1Char(':')) + ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters. + return ret; +#else + Q_UNUSED(fileName); + return QFileSystemEngine::currentPath(); +#endif +} + +QString QFSFileEngine::homePath() +{ + return QFileSystemEngine::homePath(); +} + +QString QFSFileEngine::rootPath() +{ + return QFileSystemEngine::rootPath(); +} + +QString QFSFileEngine::tempPath() +{ + return QFileSystemEngine::tempPath(); +} + +QFileInfoList QFSFileEngine::drives() +{ + QFileInfoList ret; +#if !defined(Q_OS_WINCE) +#if defined(Q_OS_WIN32) + quint32 driveBits = (quint32) GetLogicalDrives() & 0x3ffffff; +#elif defined(Q_OS_OS2EMX) + quint32 driveBits, cur; + if (DosQueryCurrentDisk(&cur, &driveBits) != NO_ERROR) + exit(1); + driveBits &= 0x3ffffff; +#endif + char driveName[] = "A:/"; + + while (driveBits) { + if (driveBits & 1) + ret.append(QFileInfo(QLatin1String(driveName))); + driveName[0]++; + driveBits = driveBits >> 1; + } + return ret; +#else + ret.append(QFileInfo(QLatin1String("/"))); + return ret; +#endif +} + +bool QFSFileEnginePrivate::doStat(QFileSystemMetaData::MetaDataFlags flags) const +{ + if (!tried_stat || !metaData.hasFlags(flags)) { + tried_stat = true; + +#if !defined(Q_OS_WINCE) + int localFd = fd; + if (fh && fileEntry.isEmpty()) + localFd = QT_FILENO(fh); + if (localFd != -1) + QFileSystemEngine::fillMetaData(localFd, metaData, flags); +#endif + if (metaData.missingFlags(flags) && !fileEntry.isEmpty()) + QFileSystemEngine::fillMetaData(fileEntry, metaData, metaData.missingFlags(flags)); + } + + return metaData.exists(); +} + + +bool QFSFileEngine::link(const QString &newName) +{ +#if !defined(Q_OS_WINCE) +#if !defined(QT_NO_LIBRARY) && !defined(Q_CC_MWERKS) + bool ret = false; + + QString linkName = newName; + //### assume that they add .lnk + + IShellLink *psl; + bool neededCoInit = false; + + HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&psl); + + if (hres == CO_E_NOTINITIALIZED) { // COM was not initialized + neededCoInit = true; + CoInitialize(NULL); + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&psl); + } + + if (SUCCEEDED(hres)) { + hres = psl->SetPath((wchar_t *)fileName(AbsoluteName).replace(QLatin1Char('/'), QLatin1Char('\\')).utf16()); + if (SUCCEEDED(hres)) { + hres = psl->SetWorkingDirectory((wchar_t *)fileName(AbsolutePathName).replace(QLatin1Char('/'), QLatin1Char('\\')).utf16()); + if (SUCCEEDED(hres)) { + IPersistFile *ppf; + hres = psl->QueryInterface(IID_IPersistFile, (void **)&ppf); + if (SUCCEEDED(hres)) { + hres = ppf->Save((wchar_t*)linkName.utf16(), TRUE); + if (SUCCEEDED(hres)) + ret = true; + ppf->Release(); + } + } + } + psl->Release(); + } + if (!ret) + setError(QFile::RenameError, qt_error_string()); + + if (neededCoInit) + CoUninitialize(); + + return ret; +#else + Q_UNUSED(newName); + return false; +#endif // QT_NO_LIBRARY +#else + QString linkName = newName; + if (!linkName.endsWith(QLatin1String(".lnk"))) + linkName += QLatin1String(".lnk"); + QString orgName = fileName(AbsoluteName).replace(QLatin1Char('/'), QLatin1Char('\\')); + // Need to append on our own + orgName.prepend(QLatin1Char('"')); + orgName.append(QLatin1Char('"')); + bool ret = SUCCEEDED(SHCreateShortcut((wchar_t*)linkName.utf16(), (wchar_t*)orgName.utf16())); + if (!ret) + setError(QFile::RenameError, qt_error_string()); + return ret; +#endif // Q_OS_WINCE +} + +/*! + \reimp +*/ +QAbstractFileEngine::FileFlags QFSFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const +{ + Q_D(const QFSFileEngine); + + if (type & Refresh) + d->metaData.clear(); + + QAbstractFileEngine::FileFlags ret = 0; + + if (type & FlagsMask) + ret |= LocalDiskFlag; + + bool exists; + { + QFileSystemMetaData::MetaDataFlags queryFlags = 0; + + queryFlags |= QFileSystemMetaData::MetaDataFlags(uint(type)) + & QFileSystemMetaData::Permissions; + + // AliasType and BundleType are 0x0 + if (type & TypesMask) + queryFlags |= QFileSystemMetaData::AliasType + | QFileSystemMetaData::LinkType + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::BundleType; + + if (type & FlagsMask) + queryFlags |= QFileSystemMetaData::HiddenAttribute + | QFileSystemMetaData::ExistsAttribute; + + queryFlags |= QFileSystemMetaData::LinkType; + + exists = d->doStat(queryFlags); + } + + if (exists && (type & PermsMask)) + ret |= FileFlags(uint(d->metaData.permissions())); + + if (type & TypesMask) { + if ((type & LinkType) && d->metaData.isLegacyLink()) + ret |= LinkType; + if (d->metaData.isDirectory()) { + ret |= DirectoryType; + } else { + ret |= FileType; + } + } + if (type & FlagsMask) { + if (d->metaData.exists()) { + ret |= ExistsFlag; + if (d->fileEntry.isRoot()) + ret |= RootFlag; + else if (d->metaData.isHidden()) + ret |= HiddenFlag; + } + } + return ret; +} + +QString QFSFileEngine::fileName(FileName file) const +{ + Q_D(const QFSFileEngine); + if (file == BaseName) { + return d->fileEntry.fileName(); + } else if (file == PathName) { + return d->fileEntry.path(); + } else if (file == AbsoluteName || file == AbsolutePathName) { + QString ret; + + if (!isRelativePath()) { +#if !defined(Q_OS_WINCE) + if (d->fileEntry.filePath().startsWith(QLatin1Char('/')) || // It's a absolute path to the current drive, so \a.txt -> Z:\a.txt + d->fileEntry.filePath().size() == 2 || // It's a drive letter that needs to get a working dir appended + (d->fileEntry.filePath().size() > 2 && d->fileEntry.filePath().at(2) != QLatin1Char('/')) || // It's a drive-relative path, so Z:a.txt -> Z:\currentpath\a.txt + d->fileEntry.filePath().contains(QLatin1String("/../")) || d->fileEntry.filePath().contains(QLatin1String("/./")) || + d->fileEntry.filePath().endsWith(QLatin1String("/..")) || d->fileEntry.filePath().endsWith(QLatin1String("/."))) + { + ret = QDir::fromNativeSeparators(QFileSystemEngine::nativeAbsoluteFilePath(d->fileEntry.filePath())); + } else +#endif + { + ret = d->fileEntry.filePath(); + } + } else { + ret = QDir::cleanPath(QDir::currentPath() + QLatin1Char('/') + d->fileEntry.filePath()); + } + + // The path should be absolute at this point. + // From the docs : + // Absolute paths begin with the directory separator "/" + // (optionally preceded by a drive specification under Windows). + if (ret.at(0) != QLatin1Char('/')) { + Q_ASSERT(ret.length() >= 2); + Q_ASSERT(ret.at(0).isLetter()); + Q_ASSERT(ret.at(1) == QLatin1Char(':')); + + // Force uppercase drive letters. + ret[0] = ret.at(0).toUpper(); + } + + if (file == AbsolutePathName) { + int slash = ret.lastIndexOf(QLatin1Char('/')); + if (slash < 0) + return ret; + else if (ret.at(0) != QLatin1Char('/') && slash == 2) + return ret.left(3); // include the slash + else + return ret.left(slash > 0 ? slash : 1); + } + return ret; + } else if (file == CanonicalName || file == CanonicalPathName) { + if (!(fileFlags(ExistsFlag) & ExistsFlag)) + return QString(); + QFileSystemEntry entry(QFileSystemEngine::canonicalName(QFileSystemEntry(fileName(AbsoluteName)), d->metaData)); + + if (file == CanonicalPathName) + return entry.path(); + return entry.filePath(); + } else if (file == LinkName) { + return QFileSystemEngine::getLinkTarget(d->fileEntry, d->metaData).filePath(); + } else if (file == BundleName) { + return QString(); + } + return d->fileEntry.filePath(); +} + +bool QFSFileEngine::isRelativePath() const +{ + Q_D(const QFSFileEngine); + // drive, e.g. "a:", or UNC root, e.q. "//" + return d->fileEntry.isRelative(); +} + +uint QFSFileEngine::ownerId(FileOwner /*own*/) const +{ + static const uint nobodyID = (uint) -2; + return nobodyID; +} + +QString QFSFileEngine::owner(FileOwner own) const +{ + Q_D(const QFSFileEngine); + return QFileSystemEngine::owner(d->fileEntry, own); +} + +bool QFSFileEngine::setPermissions(uint perms) +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::setPermissions(d->fileEntry, QFile::Permissions(perms), error); + if (!ret) + setError(QFile::PermissionsError, error.toString()); + return ret; +} + +bool QFSFileEngine::setSize(qint64 size) +{ + Q_D(QFSFileEngine); + + if (d->fileHandle != INVALID_HANDLE_VALUE || d->fd != -1 || d->fh) { + // resize open file + HANDLE fh = d->fileHandle; +#if !defined(Q_OS_WINCE) + if (fh == INVALID_HANDLE_VALUE) { + if (d->fh) + fh = (HANDLE)_get_osfhandle(QT_FILENO(d->fh)); + else + fh = (HANDLE)_get_osfhandle(d->fd); + } +#endif + if (fh == INVALID_HANDLE_VALUE) + return false; + qint64 currentPos = pos(); + + if (seek(size) && SetEndOfFile(fh)) { + seek(qMin(currentPos, size)); + return true; + } + + seek(currentPos); + return false; + } + + if (!d->fileEntry.isEmpty()) { + // resize file on disk + QFile file(d->fileEntry.filePath()); + if (file.open(QFile::ReadWrite)) { + bool ret = file.resize(size); + if (!ret) + setError(QFile::ResizeError, file.errorString()); + return ret; + } + } + return false; +} + + +QDateTime QFSFileEngine::fileTime(FileTime time) const +{ + Q_D(const QFSFileEngine); + + if (d->doStat(QFileSystemMetaData::Times)) + return d->metaData.fileTime(time); + + return QDateTime(); +} + +uchar *QFSFileEnginePrivate::map(qint64 offset, qint64 size, + QFile::MemoryMapFlags flags) +{ + Q_Q(QFSFileEngine); + Q_UNUSED(flags); + if (openMode == QFile::NotOpen) { + q->setError(QFile::PermissionsError, qt_error_string(ERROR_ACCESS_DENIED)); + return 0; + } + if (offset == 0 && size == 0) { + q->setError(QFile::UnspecifiedError, qt_error_string(ERROR_INVALID_PARAMETER)); + return 0; + } + + if (mapHandle == INVALID_HANDLE_VALUE) { + // get handle to the file + HANDLE handle = fileHandle; + +#ifndef Q_OS_WINCE + if (handle == INVALID_HANDLE_VALUE && fh) + handle = (HANDLE)::_get_osfhandle(QT_FILENO(fh)); +#endif + +#ifdef Q_USE_DEPRECATED_MAP_API + nativeClose(); + // handle automatically closed by kernel with mapHandle (below). + handle = ::CreateFileForMapping((const wchar_t*)fileEntry.nativeFilePath().utf16(), + GENERIC_READ | (openMode & QIODevice::WriteOnly ? GENERIC_WRITE : 0), + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + // Since this is a special case, we check if the return value was NULL and if so + // we change it to INVALID_HANDLE_VALUE to follow the logic inside this function. + if(0 == handle) + handle = INVALID_HANDLE_VALUE; +#endif + + if (handle == INVALID_HANDLE_VALUE) { + q->setError(QFile::PermissionsError, qt_error_string(ERROR_ACCESS_DENIED)); + return 0; + } + + // first create the file mapping handle + DWORD protection = (openMode & QIODevice::WriteOnly) ? PAGE_READWRITE : PAGE_READONLY; + mapHandle = ::CreateFileMapping(handle, 0, protection, 0, 0, 0); + if (mapHandle == INVALID_HANDLE_VALUE) { + q->setError(QFile::PermissionsError, qt_error_string()); +#ifdef Q_USE_DEPRECATED_MAP_API + ::CloseHandle(handle); +#endif + return 0; + } + } + + // setup args to map + DWORD access = 0; + if (openMode & QIODevice::ReadOnly) access = FILE_MAP_READ; + if (openMode & QIODevice::WriteOnly) access = FILE_MAP_WRITE; + + DWORD offsetHi = offset >> 32; + DWORD offsetLo = offset & Q_UINT64_C(0xffffffff); + SYSTEM_INFO sysinfo; + ::GetSystemInfo(&sysinfo); + DWORD mask = sysinfo.dwAllocationGranularity - 1; + DWORD extra = offset & mask; + if (extra) + offsetLo &= ~mask; + + // attempt to create the map + LPVOID mapAddress = ::MapViewOfFile(mapHandle, access, + offsetHi, offsetLo, size + extra); + if (mapAddress) { + uchar *address = extra + static_cast<uchar*>(mapAddress); + maps[address] = extra; + return address; + } + + switch(GetLastError()) { + case ERROR_ACCESS_DENIED: + q->setError(QFile::PermissionsError, qt_error_string()); + break; + case ERROR_INVALID_PARAMETER: + // size are out of bounds + default: + q->setError(QFile::UnspecifiedError, qt_error_string()); + } + + ::CloseHandle(mapHandle); + return 0; +} + +bool QFSFileEnginePrivate::unmap(uchar *ptr) +{ + Q_Q(QFSFileEngine); + if (!maps.contains(ptr)) { + q->setError(QFile::PermissionsError, qt_error_string(ERROR_ACCESS_DENIED)); + return false; + } + uchar *start = ptr - maps[ptr]; + if (!UnmapViewOfFile(start)) { + q->setError(QFile::PermissionsError, qt_error_string()); + return false; + } + + maps.remove(ptr); + if (maps.isEmpty()) { + ::CloseHandle(mapHandle); + mapHandle = INVALID_HANDLE_VALUE; + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qiodevice.cpp b/src/corelib/io/qiodevice.cpp new file mode 100644 index 0000000000..8759f8b4c4 --- /dev/null +++ b/src/corelib/io/qiodevice.cpp @@ -0,0 +1,1846 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QIODEVICE_DEBUG + +#include "qbytearray.h" +#include "qdebug.h" +#include "qiodevice_p.h" +#include "qfile.h" +#include "qstringlist.h" +#include <limits.h> + +#ifdef QIODEVICE_DEBUG +# include <ctype.h> +#endif + +QT_BEGIN_NAMESPACE + +#ifdef QIODEVICE_DEBUG +void debugBinaryString(const QByteArray &input) +{ + QByteArray tmp; + int startOffset = 0; + for (int i = 0; i < input.size(); ++i) { + tmp += input[i]; + + if ((i % 16) == 15 || i == (input.size() - 1)) { + printf("\n%15d:", startOffset); + startOffset += tmp.size(); + + for (int j = 0; j < tmp.size(); ++j) + printf(" %02x", int(uchar(tmp[j]))); + for (int j = tmp.size(); j < 16 + 1; ++j) + printf(" "); + for (int j = 0; j < tmp.size(); ++j) + printf("%c", isprint(int(uchar(tmp[j]))) ? tmp[j] : '.'); + tmp.clear(); + } + } + printf("\n\n"); +} + +void debugBinaryString(const char *data, qint64 maxlen) +{ + debugBinaryString(QByteArray(data, maxlen)); +} +#endif + +#define Q_VOID + +#define CHECK_MAXLEN(function, returnType) \ + do { \ + if (maxSize < 0) { \ + qWarning("QIODevice::"#function": Called with maxSize < 0"); \ + return returnType; \ + } \ + } while (0) + +#define CHECK_WRITABLE(function, returnType) \ + do { \ + if ((d->openMode & WriteOnly) == 0) { \ + if (d->openMode == NotOpen) \ + return returnType; \ + qWarning("QIODevice::"#function": ReadOnly device"); \ + return returnType; \ + } \ + } while (0) + +#define CHECK_READABLE(function, returnType) \ + do { \ + if ((d->openMode & ReadOnly) == 0) { \ + if (d->openMode == NotOpen) \ + return returnType; \ + qWarning("QIODevice::"#function": WriteOnly device"); \ + return returnType; \ + } \ + } while (0) + +/*! \internal + */ +QIODevicePrivate::QIODevicePrivate() + : openMode(QIODevice::NotOpen), buffer(QIODEVICE_BUFFERSIZE), + pos(0), devicePos(0) + , pPos(&pos), pDevicePos(&devicePos) + , baseReadLineDataCalled(false) + , firstRead(true) + , accessMode(Unset) +#ifdef QT_NO_QOBJECT + , q_ptr(0) +#endif +{ +} + +/*! \internal + */ +QIODevicePrivate::~QIODevicePrivate() +{ +} + +/*! + \class QIODevice + \reentrant + + \brief The QIODevice class is the base interface class of all I/O + devices in Qt. + + \ingroup io + + QIODevice provides both a common implementation and an abstract + interface for devices that support reading and writing of blocks + of data, such as QFile, QBuffer and QTcpSocket. QIODevice is + abstract and can not be instantiated, but it is common to use the + interface it defines to provide device-independent I/O features. + For example, Qt's XML classes operate on a QIODevice pointer, + allowing them to be used with various devices (such as files and + buffers). + + Before accessing the device, open() must be called to set the + correct OpenMode (such as ReadOnly or ReadWrite). You can then + write to the device with write() or putChar(), and read by calling + either read(), readLine(), or readAll(). Call close() when you are + done with the device. + + QIODevice distinguishes between two types of devices: + random-access devices and sequential devices. + + \list + \o Random-access devices support seeking to arbitrary + positions using seek(). The current position in the file is + available by calling pos(). QFile and QBuffer are examples of + random-access devices. + + \o Sequential devices don't support seeking to arbitrary + positions. The data must be read in one pass. The functions + pos() and size() don't work for sequential devices. + QTcpSocket and QProcess are examples of sequential devices. + \endlist + + You can use isSequential() to determine the type of device. + + QIODevice emits readyRead() when new data is available for + reading; for example, if new data has arrived on the network or if + additional data is appended to a file that you are reading + from. You can call bytesAvailable() to determine the number of + bytes that are currently available for reading. It's common to use + bytesAvailable() together with the readyRead() signal when + programming with asynchronous devices such as QTcpSocket, where + fragments of data can arrive at arbitrary points in + time. QIODevice emits the bytesWritten() signal every time a + payload of data has been written to the device. Use bytesToWrite() + to determine the current amount of data waiting to be written. + + Certain subclasses of QIODevice, such as QTcpSocket and QProcess, + are asynchronous. This means that I/O functions such as write() + or read() always return immediately, while communication with the + device itself may happen when control goes back to the event loop. + QIODevice provides functions that allow you to force these + operations to be performed immediately, while blocking the + calling thread and without entering the event loop. This allows + QIODevice subclasses to be used without an event loop, or in + a separate thread: + + \list + \o waitForReadyRead() - This function suspends operation in the + calling thread until new data is available for reading. + + \o waitForBytesWritten() - This function suspends operation in the + calling thread until one payload of data has been written to the + device. + + \o waitFor....() - Subclasses of QIODevice implement blocking + functions for device-specific operations. For example, QProcess + has a function called waitForStarted() which suspends operation in + the calling thread until the process has started. + \endlist + + Calling these functions from the main, GUI thread, may cause your + user interface to freeze. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qiodevice.cpp 0 + + By subclassing QIODevice, you can provide the same interface to + your own I/O devices. Subclasses of QIODevice are only required to + implement the protected readData() and writeData() functions. + QIODevice uses these functions to implement all its convenience + functions, such as getChar(), readLine() and write(). QIODevice + also handles access control for you, so you can safely assume that + the device is opened in write mode if writeData() is called. + + Some subclasses, such as QFile and QTcpSocket, are implemented + using a memory buffer for intermediate storing of data. This + reduces the number of required device accessing calls, which are + often very slow. Buffering makes functions like getChar() and + putChar() fast, as they can operate on the memory buffer instead + of directly on the device itself. Certain I/O operations, however, + don't work well with a buffer. For example, if several users open + the same device and read it character by character, they may end + up reading the same data when they meant to read a separate chunk + each. For this reason, QIODevice allows you to bypass any + buffering by passing the Unbuffered flag to open(). When + subclassing QIODevice, remember to bypass any buffer you may use + when the device is open in Unbuffered mode. + + \sa QBuffer QFile QTcpSocket +*/ + +/*! + \typedef QIODevice::Offset + \compat + + Use \c qint64 instead. +*/ + +/*! + \typedef QIODevice::Status + \compat + + Use QIODevice::OpenMode instead, or see the documentation for + specific devices. +*/ + +/*! + \enum QIODevice::OpenModeFlag + + This enum is used with open() to describe the mode in which a device + is opened. It is also returned by openMode(). + + \value NotOpen The device is not open. + \value ReadOnly The device is open for reading. + \value WriteOnly The device is open for writing. + \value ReadWrite The device is open for reading and writing. + \value Append The device is opened in append mode, so that all data is + written to the end of the file. + \value Truncate If possible, the device is truncated before it is opened. + All earlier contents of the device are lost. + \value Text When reading, the end-of-line terminators are + translated to '\n'. When writing, the end-of-line + terminators are translated to the local encoding, for + example '\r\n' for Win32. + \value Unbuffered Any buffer in the device is bypassed. + + Certain flags, such as \c Unbuffered and \c Truncate, are + meaningless when used with some subclasses. Some of these + restrictions are implied by the type of device that is represented + by a subclass. In other cases, the restriction may be due to the + implementation, or may be imposed by the underlying platform; for + example, QTcpSocket does not support \c Unbuffered mode, and + limitations in the native API prevent QFile from supporting \c + Unbuffered on Windows. +*/ + +/*! \fn QIODevice::bytesWritten(qint64 bytes) + + This signal is emitted every time a payload of data has been + written to the device. The \a bytes argument is set to the number + of bytes that were written in this payload. + + bytesWritten() is not emitted recursively; if you reenter the event loop + or call waitForBytesWritten() inside a slot connected to the + bytesWritten() signal, the signal will not be reemitted (although + waitForBytesWritten() may still return true). + + \sa readyRead() +*/ + +/*! + \fn QIODevice::readyRead() + + This signal is emitted once every time new data is available for + reading from the device. It will only be emitted again once new + data is available, such as when a new payload of network data has + arrived on your network socket, or when a new block of data has + been appended to your device. + + readyRead() is not emitted recursively; if you reenter the event loop or + call waitForReadyRead() inside a slot connected to the readyRead() signal, + the signal will not be reemitted (although waitForReadyRead() may still + return true). + + Note for developers implementing classes derived from QIODevice: + you should always emit readyRead() when new data has arrived (do not + emit it only because there's data still to be read in your + buffers). Do not emit readyRead() in other conditions. + + \sa bytesWritten() +*/ + +/*! \fn QIODevice::aboutToClose() + + This signal is emitted when the device is about to close. Connect + this signal if you have operations that need to be performed + before the device closes (e.g., if you have data in a separate + buffer that needs to be written to the device). +*/ + +/*! + \fn QIODevice::readChannelFinished() + \since 4.4 + + This signal is emitted when the input (reading) stream is closed + in this device. It is emitted as soon as the closing is detected, + which means that there might still be data available for reading + with read(). + + \sa atEnd(), read() +*/ + +#ifdef QT_NO_QOBJECT +QIODevice::QIODevice() + : d_ptr(new QIODevicePrivate) +{ + d_ptr->q_ptr = this; +} + +/*! \internal +*/ +QIODevice::QIODevice(QIODevicePrivate &dd) + : d_ptr(&dd) +{ + d_ptr->q_ptr = this; +} +#else + +/*! + Constructs a QIODevice object. +*/ + +QIODevice::QIODevice() + : QObject(*new QIODevicePrivate, 0) +{ +#if defined QIODEVICE_DEBUG + QFile *file = qobject_cast<QFile *>(this); + printf("%p QIODevice::QIODevice(\"%s\") %s\n", this, metaObject()->className(), + qPrintable(file ? file->fileName() : QString())); +#endif +} + +/*! + Constructs a QIODevice object with the given \a parent. +*/ + +QIODevice::QIODevice(QObject *parent) + : QObject(*new QIODevicePrivate, parent) +{ +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::QIODevice(%p \"%s\")\n", this, parent, metaObject()->className()); +#endif +} + +/*! \internal +*/ +QIODevice::QIODevice(QIODevicePrivate &dd, QObject *parent) + : QObject(dd, parent) +{ +} +#endif + + +/*! + The destructor is virtual, and QIODevice is an abstract base + class. This destructor does not call close(), but the subclass + destructor might. If you are in doubt, call close() before + destroying the QIODevice. +*/ +QIODevice::~QIODevice() +{ +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::~QIODevice()\n", this); +#endif +} + +/*! + Returns true if this device is sequential; otherwise returns + false. + + Sequential devices, as opposed to a random-access devices, have no + concept of a start, an end, a size, or a current position, and they + do not support seeking. You can only read from the device when it + reports that data is available. The most common example of a + sequential device is a network socket. On Unix, special files such + as /dev/zero and fifo pipes are sequential. + + Regular files, on the other hand, do support random access. They + have both a size and a current position, and they also support + seeking backwards and forwards in the data stream. Regular files + are non-sequential. + + \sa bytesAvailable() +*/ +bool QIODevice::isSequential() const +{ + return false; +} + +/*! + Returns the mode in which the device has been opened; + i.e. ReadOnly or WriteOnly. + + \sa OpenMode +*/ +QIODevice::OpenMode QIODevice::openMode() const +{ + return d_func()->openMode; +} + +/*! + Sets the OpenMode of the device to \a openMode. Call this + function to set the open mode if the flags change after the device + has been opened. + + \sa openMode() OpenMode +*/ +void QIODevice::setOpenMode(OpenMode openMode) +{ + Q_D(QIODevice); +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::setOpenMode(0x%x)\n", this, int(openMode)); +#endif + d->openMode = openMode; + d->accessMode = QIODevicePrivate::Unset; + d->firstRead = true; + if (!isReadable()) + d->buffer.clear(); +} + +/*! + If \a enabled is true, this function sets the \l Text flag on the device; + otherwise the \l Text flag is removed. This feature is useful for classes + that provide custom end-of-line handling on a QIODevice. + + \sa open(), setOpenMode() + */ +void QIODevice::setTextModeEnabled(bool enabled) +{ + Q_D(QIODevice); + if (enabled) + d->openMode |= Text; + else + d->openMode &= ~Text; +} + +/*! + Returns true if the \l Text flag is enabled; otherwise returns false. + + \sa setTextModeEnabled() +*/ +bool QIODevice::isTextModeEnabled() const +{ + return d_func()->openMode & Text; +} + +/*! + Returns true if the device is open; otherwise returns false. A + device is open if it can be read from and/or written to. By + default, this function returns false if openMode() returns + \c NotOpen. + + \sa openMode() OpenMode +*/ +bool QIODevice::isOpen() const +{ + return d_func()->openMode != NotOpen; +} + +/*! + Returns true if data can be read from the device; otherwise returns + false. Use bytesAvailable() to determine how many bytes can be read. + + This is a convenience function which checks if the OpenMode of the + device contains the ReadOnly flag. + + \sa openMode() OpenMode +*/ +bool QIODevice::isReadable() const +{ + return (openMode() & ReadOnly) != 0; +} + +/*! + Returns true if data can be written to the device; otherwise returns + false. + + This is a convenience function which checks if the OpenMode of the + device contains the WriteOnly flag. + + \sa openMode() OpenMode +*/ +bool QIODevice::isWritable() const +{ + return (openMode() & WriteOnly) != 0; +} + +/*! + Opens the device and sets its OpenMode to \a mode. Returns true if successful; + otherwise returns false. This function should be called from any + reimplementations of open() or other functions that open the device. + + \sa openMode() OpenMode +*/ +bool QIODevice::open(OpenMode mode) +{ + Q_D(QIODevice); + d->openMode = mode; + d->pos = (mode & Append) ? size() : qint64(0); + d->buffer.clear(); + d->accessMode = QIODevicePrivate::Unset; + d->firstRead = true; +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::open(0x%x)\n", this, quint32(mode)); +#endif + return true; +} + +/*! + First emits aboutToClose(), then closes the device and sets its + OpenMode to NotOpen. The error string is also reset. + + \sa setOpenMode() OpenMode +*/ +void QIODevice::close() +{ + Q_D(QIODevice); + if (d->openMode == NotOpen) + return; + +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::close()\n", this); +#endif + +#ifndef QT_NO_QOBJECT + emit aboutToClose(); +#endif + d->openMode = NotOpen; + d->errorString.clear(); + d->pos = 0; + d->buffer.clear(); + d->firstRead = true; +} + +/*! + For random-access devices, this function returns the position that + data is written to or read from. For sequential devices or closed + devices, where there is no concept of a "current position", 0 is + returned. + + The current read/write position of the device is maintained internally by + QIODevice, so reimplementing this function is not necessary. When + subclassing QIODevice, use QIODevice::seek() to notify QIODevice about + changes in the device position. + + \sa isSequential(), seek() +*/ +qint64 QIODevice::pos() const +{ + Q_D(const QIODevice); +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::pos() == %d\n", this, int(d->pos)); +#endif + return d->pos; +} + +/*! + For open random-access devices, this function returns the size of the + device. For open sequential devices, bytesAvailable() is returned. + + If the device is closed, the size returned will not reflect the actual + size of the device. + + \sa isSequential(), pos() +*/ +qint64 QIODevice::size() const +{ + return d_func()->isSequential() ? bytesAvailable() : qint64(0); +} + +/*! + For random-access devices, this function sets the current position + to \a pos, returning true on success, or false if an error occurred. + For sequential devices, the default behavior is to do nothing and + return false. + + When subclassing QIODevice, you must call QIODevice::seek() at the + start of your function to ensure integrity with QIODevice's + built-in buffer. The base implementation always returns true. + + \sa pos(), isSequential() +*/ +bool QIODevice::seek(qint64 pos) +{ + Q_D(QIODevice); + if (d->openMode == NotOpen) { + qWarning("QIODevice::seek: The device is not open"); + return false; + } + if (pos < 0) { + qWarning("QIODevice::seek: Invalid pos: %d", int(pos)); + return false; + } + +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::seek(%d), before: d->pos = %d, d->buffer.size() = %d\n", + this, int(pos), int(d->pos), d->buffer.size()); +#endif + + qint64 offset = pos - d->pos; + if (!d->isSequential()) { + d->pos = pos; + d->devicePos = pos; + } + + if (offset < 0 + || offset >= qint64(d->buffer.size())) + // When seeking backwards, an operation that is only allowed for + // random-access devices, the buffer is cleared. The next read + // operation will then refill the buffer. We can optimize this, if we + // find that seeking backwards becomes a significant performance hit. + d->buffer.clear(); + else if (!d->buffer.isEmpty()) + d->buffer.skip(int(offset)); + +#if defined QIODEVICE_DEBUG + printf("%p \tafter: d->pos == %d, d->buffer.size() == %d\n", this, int(d->pos), + d->buffer.size()); +#endif + return true; +} + +/*! + Returns true if the current read and write position is at the end + of the device (i.e. there is no more data available for reading on + the device); otherwise returns false. + + For some devices, atEnd() can return true even though there is more data + to read. This special case only applies to devices that generate data in + direct response to you calling read() (e.g., \c /dev or \c /proc files on + Unix and Mac OS X, or console input / \c stdin on all platforms). + + \sa bytesAvailable(), read(), isSequential() +*/ +bool QIODevice::atEnd() const +{ + Q_D(const QIODevice); +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::atEnd() returns %s, d->openMode == %d, d->pos == %d\n", this, (d->openMode == NotOpen || d->pos == size()) ? "true" : "false", + int(d->openMode), int(d->pos)); +#endif + return d->openMode == NotOpen || (d->buffer.isEmpty() && bytesAvailable() == 0); +} + +/*! + Seeks to the start of input for random-access devices. Returns + true on success; otherwise returns false (for example, if the + device is not open). + + Note that when using a QTextStream on a QFile, calling reset() on + the QFile will not have the expected result because QTextStream + buffers the file. Use the QTextStream::seek() function instead. + + \sa seek() +*/ +bool QIODevice::reset() +{ +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::reset()\n", this); +#endif + return seek(0); +} + +/*! + Returns the number of bytes that are available for reading. This + function is commonly used with sequential devices to determine the + number of bytes to allocate in a buffer before reading. + + Subclasses that reimplement this function must call the base + implementation in order to include the size of QIODevices' buffer. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qiodevice.cpp 1 + + \sa bytesToWrite(), readyRead(), isSequential() +*/ +qint64 QIODevice::bytesAvailable() const +{ + Q_D(const QIODevice); + if (!d->isSequential()) + return qMax(size() - d->pos, qint64(0)); + return d->buffer.size(); +} + +/*! + For buffered devices, this function returns the number of bytes + waiting to be written. For devices with no buffer, this function + returns 0. + + \sa bytesAvailable(), bytesWritten(), isSequential() +*/ +qint64 QIODevice::bytesToWrite() const +{ + return qint64(0); +} + +#ifdef Q_CC_RVCT +// arm mode makes the 64-bit integer operations much faster in RVCT 2.2 +#pragma push +#pragma arm +#endif + +/*! + Reads at most \a maxSize bytes from the device into \a data, and + returns the number of bytes read. If an error occurs, such as when + attempting to read from a device opened in WriteOnly mode, this + function returns -1. + + 0 is returned when no more data is available for reading. However, + reading past the end of the stream is considered an error, so this + function returns -1 in those cases (that is, reading on a closed + socket or after a process has died). + + \sa readData() readLine() write() +*/ +qint64 QIODevice::read(char *data, qint64 maxSize) +{ + Q_D(QIODevice); + +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::read(%p, %d), d->pos = %d, d->buffer.size() = %d\n", + this, data, int(maxSize), int(d->pos), int(d->buffer.size())); +#endif + + // Short circuit for getChar() + if (maxSize == 1) { + int chint; + while ((chint = d->buffer.getChar()) != -1) { + ++(*d->pPos); + + char c = char(uchar(chint)); + if (c == '\r' && (d->openMode & Text)) + continue; + *data = c; +#if defined QIODEVICE_DEBUG + printf("%p \tread 0x%hhx (%c) returning 1 (shortcut)\n", this, + int(c), isprint(c) ? c : '?'); +#endif + return qint64(1); + } + } + + CHECK_MAXLEN(read, qint64(-1)); + qint64 readSoFar = 0; + bool moreToRead = true; + do { + // Try reading from the buffer. + int lastReadChunkSize = d->buffer.read(data, maxSize); + if (lastReadChunkSize > 0) { + *d->pPos += lastReadChunkSize; + readSoFar += lastReadChunkSize; + // fast exit when satisfied by buffer + if (lastReadChunkSize == maxSize && !(d->openMode & Text)) + return readSoFar; + + data += lastReadChunkSize; + maxSize -= lastReadChunkSize; +#if defined QIODEVICE_DEBUG + printf("%p \treading %d bytes from buffer into position %d\n", this, lastReadChunkSize, + int(readSoFar) - lastReadChunkSize); +#endif + } else { + if (d->firstRead) { + // this is the first time the file has been read, check it's valid and set up pos pointers + // for fast pos updates. + CHECK_READABLE(read, qint64(-1)); + d->firstRead = false; + if (d->isSequential()) { + d->pPos = &d->seqDumpPos; + d->pDevicePos = &d->seqDumpPos; + } + } + + if (!maxSize) + return readSoFar; + + if ((d->openMode & Unbuffered) == 0 && maxSize < QIODEVICE_BUFFERSIZE) { + // In buffered mode, we try to fill up the QIODevice buffer before + // we do anything else. + // buffer is empty at this point, try to fill it + int bytesToBuffer = QIODEVICE_BUFFERSIZE; + char *writePointer = d->buffer.reserve(bytesToBuffer); + + // Make sure the device is positioned correctly. + if (d->pos != d->devicePos && !d->isSequential() && !seek(d->pos)) + return readSoFar ? readSoFar : qint64(-1); + qint64 readFromDevice = readData(writePointer, bytesToBuffer); + d->buffer.chop(bytesToBuffer - (readFromDevice < 0 ? 0 : int(readFromDevice))); + + if (readFromDevice > 0) { + *d->pDevicePos += readFromDevice; +#if defined QIODEVICE_DEBUG + printf("%p \treading %d from device into buffer\n", this, int(readFromDevice)); +#endif + + if (!d->buffer.isEmpty()) { + lastReadChunkSize = d->buffer.read(data, maxSize); + readSoFar += lastReadChunkSize; + data += lastReadChunkSize; + maxSize -= lastReadChunkSize; + *d->pPos += lastReadChunkSize; +#if defined QIODEVICE_DEBUG + printf("%p \treading %d bytes from buffer at position %d\n", this, + lastReadChunkSize, int(readSoFar)); +#endif + } + } + } + } + + // If we need more, try reading from the device. + if (maxSize > 0) { + // Make sure the device is positioned correctly. + if (d->pos != d->devicePos && !d->isSequential() && !seek(d->pos)) + return readSoFar ? readSoFar : qint64(-1); + qint64 readFromDevice = readData(data, maxSize); +#if defined QIODEVICE_DEBUG + printf("%p \treading %d bytes from device (total %d)\n", this, int(readFromDevice), int(readSoFar)); +#endif + if (readFromDevice == -1 && readSoFar == 0) { + // error and we haven't read anything: return immediately + return -1; + } + if (readFromDevice > 0) { + lastReadChunkSize += int(readFromDevice); + readSoFar += readFromDevice; + data += readFromDevice; + maxSize -= readFromDevice; + *d->pPos += readFromDevice; + *d->pDevicePos += readFromDevice; + } + } + // Best attempt has been made to read data, don't try again except for text mode adjustment below + moreToRead = false; + + if (readSoFar && d->openMode & Text) { + char *readPtr = data - lastReadChunkSize; + const char *endPtr = data; + + if (readPtr < endPtr) { + // optimization to avoid initial self-assignment + while (*readPtr != '\r') { + if (++readPtr == endPtr) + return readSoFar; + } + + char *writePtr = readPtr; + + while (readPtr < endPtr) { + char ch = *readPtr++; + if (ch != '\r') + *writePtr++ = ch; + else { + --readSoFar; + --data; + ++maxSize; + } + } + + // Make sure we get more data if there is room for more. This + // is very important for when someone seeks to the start of a + // '\r\n' and reads one character - they should get the '\n'. + moreToRead = (readPtr != writePtr); + } + } + } while (moreToRead); + +#if defined QIODEVICE_DEBUG + printf("%p \treturning %d, d->pos == %d, d->buffer.size() == %d\n", this, + int(readSoFar), int(d->pos), d->buffer.size()); + debugBinaryString(data - readSoFar, readSoFar); +#endif + return readSoFar; +} + +#ifdef Q_CC_RVCT +#pragma pop +#endif + +/*! + \overload + + Reads at most \a maxSize bytes from the device, and returns the + data read as a QByteArray. + + This function has no way of reporting errors; returning an empty + QByteArray() can mean either that no data was currently available + for reading, or that an error occurred. +*/ +QByteArray QIODevice::read(qint64 maxSize) +{ + Q_D(QIODevice); + QByteArray result; + + CHECK_MAXLEN(read, result); + +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::read(%d), d->pos = %d, d->buffer.size() = %d\n", + this, int(maxSize), int(d->pos), int(d->buffer.size())); +#else + Q_UNUSED(d); +#endif + + if (maxSize != qint64(int(maxSize))) { + qWarning("QIODevice::read: maxSize argument exceeds QByteArray size limit"); + maxSize = INT_MAX; + } + + qint64 readBytes = 0; + if (maxSize) { + result.resize(int(maxSize)); + if (!result.size()) { + // If resize fails, read incrementally. + qint64 readResult; + do { + result.resize(int(qMin(maxSize, result.size() + QIODEVICE_BUFFERSIZE))); + readResult = read(result.data() + readBytes, result.size() - readBytes); + if (readResult > 0 || readBytes == 0) + readBytes += readResult; + } while (readResult == QIODEVICE_BUFFERSIZE); + } else { + readBytes = read(result.data(), result.size()); + } + } + + if (readBytes <= 0) + result.clear(); + else + result.resize(int(readBytes)); + + return result; +} + +/*! + \overload + + Reads all available data from the device, and returns it as a + QByteArray. + + This function has no way of reporting errors; returning an empty + QByteArray() can mean either that no data was currently available + for reading, or that an error occurred. +*/ +QByteArray QIODevice::readAll() +{ + Q_D(QIODevice); +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::readAll(), d->pos = %d, d->buffer.size() = %d\n", + this, int(d->pos), int(d->buffer.size())); +#endif + + QByteArray result; + qint64 readBytes = 0; + + // flush internal read buffer + if (!(d->openMode & Text) && !d->buffer.isEmpty()) { + result = d->buffer.readAll(); + readBytes = result.size(); + d->pos += readBytes; + } + + qint64 theSize; + if (d->isSequential() || (theSize = size()) == 0) { + // Size is unknown, read incrementally. + qint64 readResult; + do { + result.resize(result.size() + QIODEVICE_BUFFERSIZE); + readResult = read(result.data() + readBytes, result.size() - readBytes); + if (readResult > 0 || readBytes == 0) + readBytes += readResult; + } while (readResult > 0); + } else { + // Read it all in one go. + // If resize fails, don't read anything. + result.resize(int(readBytes + theSize - d->pos)); + readBytes += read(result.data() + readBytes, result.size() - readBytes); + } + + if (readBytes <= 0) + result.clear(); + else + result.resize(int(readBytes)); + + return result; +} + +#ifdef Q_CC_RVCT +// arm mode makes the 64-bit integer operations much faster in RVCT 2.2 +#pragma push +#pragma arm +#endif + +/*! + This function reads a line of ASCII characters from the device, up + to a maximum of \a maxSize - 1 bytes, stores the characters in \a + data, and returns the number of bytes read. If a line could not be + read but no error ocurred, this function returns 0. If an error + occurs, this function returns the length of what could be read, or + -1 if nothing was read. + + A terminating '\0' byte is always appended to \a data, so \a + maxSize must be larger than 1. + + Data is read until either of the following conditions are met: + + \list + \o The first '\n' character is read. + \o \a maxSize - 1 bytes are read. + \o The end of the device data is detected. + \endlist + + For example, the following code reads a line of characters from a + file: + + \snippet doc/src/snippets/code/src_corelib_io_qiodevice.cpp 2 + + The newline character ('\n') is included in the buffer. If a + newline is not encountered before maxSize - 1 bytes are read, a + newline will not be inserted into the buffer. On windows newline + characters are replaced with '\n'. + + This function calls readLineData(), which is implemented using + repeated calls to getChar(). You can provide a more efficient + implementation by reimplementing readLineData() in your own + subclass. + + \sa getChar(), read(), write() +*/ +qint64 QIODevice::readLine(char *data, qint64 maxSize) +{ + Q_D(QIODevice); + if (maxSize < 2) { + qWarning("QIODevice::readLine: Called with maxSize < 2"); + return qint64(-1); + } + +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::readLine(%p, %d), d->pos = %d, d->buffer.size() = %d\n", + this, data, int(maxSize), int(d->pos), int(d->buffer.size())); +#endif + + // Leave room for a '\0' + --maxSize; + + const bool sequential = d->isSequential(); + + qint64 readSoFar = 0; + if (!d->buffer.isEmpty()) { + readSoFar = d->buffer.readLine(data, maxSize); + if (!sequential) + d->pos += readSoFar; +#if defined QIODEVICE_DEBUG + printf("%p \tread from buffer: %d bytes, last character read: %hhx\n", this, + int(readSoFar), data[int(readSoFar) - 1]); + if (readSoFar) + debugBinaryString(data, int(readSoFar)); +#endif +#if defined(Q_OS_SYMBIAN) + // Open C fgets strips '\r' but readSoFar gets returned as if it was still there + if ((d->openMode & Text) && + readSoFar > 1 && + data[readSoFar - 1] == '\0' && + data[readSoFar - 2] == '\n') { + --readSoFar; + } +#endif + if (readSoFar && data[readSoFar - 1] == '\n') { + if (d->openMode & Text) { + // QRingBuffer::readLine() isn't Text aware. + if (readSoFar > 1 && data[readSoFar - 2] == '\r') { + --readSoFar; + data[readSoFar - 1] = '\n'; + } + } + data[readSoFar] = '\0'; + return readSoFar; + } + } + + if (d->pos != d->devicePos && !sequential && !seek(d->pos)) + return qint64(-1); + d->baseReadLineDataCalled = false; + qint64 readBytes = readLineData(data + readSoFar, maxSize - readSoFar); +#if defined QIODEVICE_DEBUG + printf("%p \tread from readLineData: %d bytes, readSoFar = %d bytes\n", this, + int(readBytes), int(readSoFar)); + if (readBytes > 0) { + debugBinaryString(data, int(readSoFar + readBytes)); + } +#endif + if (readBytes < 0) { + data[readSoFar] = '\0'; + return readSoFar ? readSoFar : -1; + } + readSoFar += readBytes; + if (!d->baseReadLineDataCalled && !sequential) { + d->pos += readBytes; + // If the base implementation was not called, then we must + // assume the device position is invalid and force a seek. + d->devicePos = qint64(-1); + } + data[readSoFar] = '\0'; + + if (d->openMode & Text) { +#if defined(Q_OS_SYMBIAN) + // Open C fgets strips '\r' but readSoFar gets returned as if it was still there + if (readSoFar > 1 && data[readSoFar - 1] == '\0' && data[readSoFar - 2] == '\n') { + --readSoFar; + } +#endif + if (readSoFar > 1 && data[readSoFar - 1] == '\n' && data[readSoFar - 2] == '\r') { + data[readSoFar - 2] = '\n'; + data[readSoFar - 1] = '\0'; + --readSoFar; + } + } + +#if defined QIODEVICE_DEBUG + printf("%p \treturning %d, d->pos = %d, d->buffer.size() = %d, size() = %d\n", + this, int(readSoFar), int(d->pos), d->buffer.size(), int(size())); + debugBinaryString(data, int(readSoFar)); +#endif + return readSoFar; +} + +/*! + \overload + + Reads a line from the device, but no more than \a maxSize characters, + and returns the result as a QByteArray. + + This function has no way of reporting errors; returning an empty + QByteArray() can mean either that no data was currently available + for reading, or that an error occurred. +*/ +QByteArray QIODevice::readLine(qint64 maxSize) +{ + Q_D(QIODevice); + QByteArray result; + + CHECK_MAXLEN(readLine, result); + +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::readLine(%d), d->pos = %d, d->buffer.size() = %d\n", + this, int(maxSize), int(d->pos), int(d->buffer.size())); +#else + Q_UNUSED(d); +#endif + + if (maxSize > INT_MAX) { + qWarning("QIODevice::read: maxSize argument exceeds QByteArray size limit"); + maxSize = INT_MAX; + } + + result.resize(int(maxSize)); + qint64 readBytes = 0; + if (!result.size()) { + // If resize fails or maxSize == 0, read incrementally + if (maxSize == 0) + maxSize = INT_MAX; + + // The first iteration needs to leave an extra byte for the terminating null + result.resize(1); + + qint64 readResult; + do { + result.resize(int(qMin(maxSize, result.size() + QIODEVICE_BUFFERSIZE))); + readResult = readLine(result.data() + readBytes, result.size() - readBytes); + if (readResult > 0 || readBytes == 0) + readBytes += readResult; + } while (readResult == QIODEVICE_BUFFERSIZE + && result[int(readBytes - 1)] != '\n'); + } else + readBytes = readLine(result.data(), result.size()); + + if (readBytes <= 0) + result.clear(); + else + result.resize(readBytes); + + return result; +} + +/*! + Reads up to \a maxSize characters into \a data and returns the + number of characters read. + + This function is called by readLine(), and provides its base + implementation, using getChar(). Buffered devices can improve the + performance of readLine() by reimplementing this function. + + readLine() appends a '\0' byte to \a data; readLineData() does not + need to do this. + + If you reimplement this function, be careful to return the correct + value: it should return the number of bytes read in this line, + including the terminating newline, or 0 if there is no line to be + read at this point. If an error occurs, it should return -1 if and + only if no bytes were read. Reading past EOF is considered an error. +*/ +qint64 QIODevice::readLineData(char *data, qint64 maxSize) +{ + Q_D(QIODevice); + qint64 readSoFar = 0; + char c; + int lastReadReturn = 0; + d->baseReadLineDataCalled = true; + + while (readSoFar < maxSize && (lastReadReturn = read(&c, 1)) == 1) { + *data++ = c; + ++readSoFar; + if (c == '\n') + break; + } + +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::readLineData(%p, %d), d->pos = %d, d->buffer.size() = %d, returns %d\n", + this, data, int(maxSize), int(d->pos), int(d->buffer.size()), int(readSoFar)); +#endif + if (lastReadReturn != 1 && readSoFar == 0) + return isSequential() ? lastReadReturn : -1; + return readSoFar; +} + +#ifdef Q_CC_RVCT +#pragma pop +#endif + +/*! + Returns true if a complete line of data can be read from the device; + otherwise returns false. + + Note that unbuffered devices, which have no way of determining what + can be read, always return false. + + This function is often called in conjunction with the readyRead() + signal. + + Subclasses that reimplement this function must call the base + implementation in order to include the contents of the QIODevice's buffer. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qiodevice.cpp 3 + + \sa readyRead(), readLine() +*/ +bool QIODevice::canReadLine() const +{ + return d_func()->buffer.canReadLine(); +} + +/*! + Writes at most \a maxSize bytes of data from \a data to the + device. Returns the number of bytes that were actually written, or + -1 if an error occurred. + + \sa read() writeData() +*/ +qint64 QIODevice::write(const char *data, qint64 maxSize) +{ + Q_D(QIODevice); + CHECK_WRITABLE(write, qint64(-1)); + CHECK_MAXLEN(write, qint64(-1)); + + const bool sequential = d->isSequential(); + // Make sure the device is positioned correctly. + if (d->pos != d->devicePos && !sequential && !seek(d->pos)) + return qint64(-1); + +#ifdef Q_OS_WIN + if (d->openMode & Text) { + const char *endOfData = data + maxSize; + const char *startOfBlock = data; + + qint64 writtenSoFar = 0; + + forever { + const char *endOfBlock = startOfBlock; + while (endOfBlock < endOfData && *endOfBlock != '\n') + ++endOfBlock; + + qint64 blockSize = endOfBlock - startOfBlock; + if (blockSize > 0) { + qint64 ret = writeData(startOfBlock, blockSize); + if (ret <= 0) { + if (writtenSoFar && !sequential) + d->buffer.skip(writtenSoFar); + return writtenSoFar ? writtenSoFar : ret; + } + if (!sequential) { + d->pos += ret; + d->devicePos += ret; + } + writtenSoFar += ret; + } + + if (endOfBlock == endOfData) + break; + + qint64 ret = writeData("\r\n", 2); + if (ret <= 0) { + if (writtenSoFar && !sequential) + d->buffer.skip(writtenSoFar); + return writtenSoFar ? writtenSoFar : ret; + } + if (!sequential) { + d->pos += ret; + d->devicePos += ret; + } + ++writtenSoFar; + + startOfBlock = endOfBlock + 1; + } + + if (writtenSoFar && !sequential) + d->buffer.skip(writtenSoFar); + return writtenSoFar; + } +#endif + + qint64 written = writeData(data, maxSize); + if (written > 0) { + if (!sequential) { + d->pos += written; + d->devicePos += written; + } + if (!d->buffer.isEmpty() && !sequential) + d->buffer.skip(written); + } + return written; +} + +/*! + \since 4.5 + + \overload + + Writes data from a zero-terminated string of 8-bit characters to the + device. Returns the number of bytes that were actually written, or + -1 if an error occurred. This is equivalent to + \code + ... + QIODevice::write(data, qstrlen(data)); + ... + \endcode + + \sa read() writeData() +*/ +qint64 QIODevice::write(const char *data) +{ + return write(data, qstrlen(data)); +} + +/*! \fn qint64 QIODevice::write(const QByteArray &byteArray) + + \overload + + Writes the content of \a byteArray to the device. Returns the number of + bytes that were actually written, or -1 if an error occurred. + + \sa read() writeData() +*/ + +/*! + Puts the character \a c back into the device, and decrements the + current position unless the position is 0. This function is + usually called to "undo" a getChar() operation, such as when + writing a backtracking parser. + + If \a c was not previously read from the device, the behavior is + undefined. +*/ +void QIODevice::ungetChar(char c) +{ + Q_D(QIODevice); + CHECK_READABLE(read, Q_VOID); + +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::ungetChar(0x%hhx '%c')\n", this, c, isprint(c) ? c : '?'); +#endif + + d->buffer.ungetChar(c); + if (!d->isSequential()) + --d->pos; +} + +/*! \fn bool QIODevice::putChar(char c) + + Writes the character \a c to the device. Returns true on success; + otherwise returns false. + + \sa write() getChar() ungetChar() +*/ +bool QIODevice::putChar(char c) +{ + return d_func()->putCharHelper(c); +} + +/*! + \internal +*/ +bool QIODevicePrivate::putCharHelper(char c) +{ + return q_func()->write(&c, 1) == 1; +} + +/*! + \internal +*/ +qint64 QIODevicePrivate::peek(char *data, qint64 maxSize) +{ + qint64 readBytes = q_func()->read(data, maxSize); + if (readBytes <= 0) + return readBytes; + + buffer.ungetBlock(data, readBytes); + *pPos -= readBytes; + return readBytes; +} + +/*! + \internal +*/ +QByteArray QIODevicePrivate::peek(qint64 maxSize) +{ + QByteArray result = q_func()->read(maxSize); + + if (result.isEmpty()) + return result; + + buffer.ungetBlock(result.constData(), result.size()); + *pPos -= result.size(); + return result; +} + +/*! \fn bool QIODevice::getChar(char *c) + + Reads one character from the device and stores it in \a c. If \a c + is 0, the character is discarded. Returns true on success; + otherwise returns false. + + \sa read() putChar() ungetChar() +*/ +bool QIODevice::getChar(char *c) +{ + // readability checked in read() + char ch; + return (1 == read(c ? c : &ch, 1)); +} + +/*! + \since 4.1 + + Reads at most \a maxSize bytes from the device into \a data, without side + effects (i.e., if you call read() after peek(), you will get the same + data). Returns the number of bytes read. If an error occurs, such as + when attempting to peek a device opened in WriteOnly mode, this function + returns -1. + + 0 is returned when no more data is available for reading. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qiodevice.cpp 4 + + \sa read() +*/ +qint64 QIODevice::peek(char *data, qint64 maxSize) +{ + return d_func()->peek(data, maxSize); +} + +/*! + \since 4.1 + \overload + + Peeks at most \a maxSize bytes from the device, returning the data peeked + as a QByteArray. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qiodevice.cpp 5 + + This function has no way of reporting errors; returning an empty + QByteArray() can mean either that no data was currently available + for peeking, or that an error occurred. + + \sa read() +*/ +QByteArray QIODevice::peek(qint64 maxSize) +{ + return d_func()->peek(maxSize); +} + +/*! + Blocks until new data is available for reading and the readyRead() + signal has been emitted, or until \a msecs milliseconds have + passed. If msecs is -1, this function will not time out. + + Returns true if new data is available for reading; otherwise returns + false (if the operation timed out or if an error occurred). + + This function can operate without an event loop. It is + useful when writing non-GUI applications and when performing + I/O operations in a non-GUI thread. + + If called from within a slot connected to the readyRead() signal, + readyRead() will not be reemitted. + + Reimplement this function to provide a blocking API for a custom + device. The default implementation does nothing, and returns false. + + \warning Calling this function from the main (GUI) thread + might cause your user interface to freeze. + + \sa waitForBytesWritten() +*/ +bool QIODevice::waitForReadyRead(int msecs) +{ + Q_UNUSED(msecs); + return false; +} + +/*! + For buffered devices, this function waits until a payload of + buffered written data has been written to the device and the + bytesWritten() signal has been emitted, or until \a msecs + milliseconds have passed. If msecs is -1, this function will + not time out. For unbuffered devices, it returns immediately. + + Returns true if a payload of data was written to the device; + otherwise returns false (i.e. if the operation timed out, or if an + error occurred). + + This function can operate without an event loop. It is + useful when writing non-GUI applications and when performing + I/O operations in a non-GUI thread. + + If called from within a slot connected to the bytesWritten() signal, + bytesWritten() will not be reemitted. + + Reimplement this function to provide a blocking API for a custom + device. The default implementation does nothing, and returns false. + + \warning Calling this function from the main (GUI) thread + might cause your user interface to freeze. + + \sa waitForReadyRead() +*/ +bool QIODevice::waitForBytesWritten(int msecs) +{ + Q_UNUSED(msecs); + return false; +} + +/*! + Sets the human readable description of the last device error that + occurred to \a str. + + \sa errorString() +*/ +void QIODevice::setErrorString(const QString &str) +{ + d_func()->errorString = str; +} + +/*! + Returns a human-readable description of the last device error that + occurred. + + \sa setErrorString() +*/ +QString QIODevice::errorString() const +{ + Q_D(const QIODevice); + if (d->errorString.isEmpty()) { +#ifdef QT_NO_QOBJECT + return QLatin1String(QT_TRANSLATE_NOOP(QIODevice, "Unknown error")); +#else + return tr("Unknown error"); +#endif + } + return d->errorString; +} + +/*! + \fn qint64 QIODevice::readData(char *data, qint64 maxSize) + + Reads up to \a maxSize bytes from the device into \a data, and + returns the number of bytes read or -1 if an error occurred. + + If there are no bytes to be read and there can never be more bytes + available (examples include socket closed, pipe closed, sub-process + finished), this function returns -1. + + This function is called by QIODevice. Reimplement this function + when creating a subclass of QIODevice. + + When reimplementing this function it is important that this function + reads all the required data before returning. This is required in order + for QDataStream to be able to operate on the class. QDataStream assumes + all the requested information was read and therefore does not retry reading + if there was a problem. + + \sa read() readLine() writeData() +*/ + +/*! + \fn qint64 QIODevice::writeData(const char *data, qint64 maxSize) + + Writes up to \a maxSize bytes from \a data to the device. Returns + the number of bytes written, or -1 if an error occurred. + + This function is called by QIODevice. Reimplement this function + when creating a subclass of QIODevice. + + When reimplementing this function it is important that this function + writes all the data available before returning. This is required in order + for QDataStream to be able to operate on the class. QDataStream assumes + all the information was written and therefore does not retry writing if + there was a problem. + + \sa read() write() +*/ + +/*! + \fn QIODevice::Offset QIODevice::status() const + + For device specific error handling, please refer to the + individual device documentation. + + \sa qobject_cast() +*/ + +/*! + \fn QIODevice::Offset QIODevice::at() const + + Use pos() instead. +*/ + +/*! + \fn bool QIODevice::at(Offset offset) + + Use seek(\a offset) instead. +*/ + +/*! \fn int QIODevice::flags() const + + Use openMode() instead. +*/ + +/*! \fn int QIODevice::getch() + + Use getChar() instead. +*/ + +/*! + \fn bool QIODevice::isAsynchronous() const + + This functionality is no longer available. This function always + returns true. +*/ + +/*! + \fn bool QIODevice::isBuffered() const + + Use !(openMode() & QIODevice::Unbuffered) instead. +*/ + +/*! + \fn bool QIODevice::isCombinedAccess() const + + Use openMode() instead. +*/ + +/*! + \fn bool QIODevice::isDirectAccess() const + + Use !isSequential() instead. +*/ + +/*! + \fn bool QIODevice::isInactive() const + + Use isOpen(), isReadable(), or isWritable() instead. +*/ + +/*! + \fn bool QIODevice::isRaw() const + + Use openMode() instead. +*/ + +/*! + \fn bool QIODevice::isSequentialAccess() const + + Use isSequential() instead. +*/ + +/*! + \fn bool QIODevice::isSynchronous() const + + This functionality is no longer available. This function always + returns false. +*/ + +/*! + \fn bool QIODevice::isTranslated() const + + Use openMode() instead. +*/ + +/*! + \fn bool QIODevice::mode() const + + Use openMode() instead. +*/ + +/*! \fn int QIODevice::putch(int ch) + + Use putChar(\a ch) instead. +*/ + +/*! \fn int QIODevice::ungetch(int ch) + + Use ungetChar(\a ch) instead. +*/ + +/*! + \fn quint64 QIODevice::readBlock(char *data, quint64 size) + + Use read(\a data, \a size) instead. +*/ + +/*! \fn int QIODevice::state() const + + Use isOpen() instead. +*/ + +/*! + \fn qint64 QIODevice::writeBlock(const char *data, quint64 size) + + Use write(\a data, \a size) instead. +*/ + +/*! + \fn qint64 QIODevice::writeBlock(const QByteArray &data) + + Use write(\a data) instead. +*/ + +#if defined QT3_SUPPORT +QIODevice::Status QIODevice::status() const +{ +#if !defined(QT_NO_QOBJECT) + const QFile *f = qobject_cast<const QFile *>(this); + if (f) return (int) f->error(); +#endif + return isOpen() ? 0 /* IO_Ok */ : 8 /* IO_UnspecifiedError */; +} + +/*! + For device specific error handling, please refer to the + individual device documentation. + + \sa qobject_cast() +*/ +void QIODevice::resetStatus() +{ +#if !defined(QT_NO_QOBJECT) + QFile *f = qobject_cast<QFile *>(this); + if (f) f->unsetError(); +#endif +} +#endif + +#if !defined(QT_NO_DEBUG_STREAM) +QDebug operator<<(QDebug debug, QIODevice::OpenMode modes) +{ + debug << "OpenMode("; + QStringList modeList; + if (modes == QIODevice::NotOpen) { + modeList << QLatin1String("NotOpen"); + } else { + if (modes & QIODevice::ReadOnly) + modeList << QLatin1String("ReadOnly"); + if (modes & QIODevice::WriteOnly) + modeList << QLatin1String("WriteOnly"); + if (modes & QIODevice::Append) + modeList << QLatin1String("Append"); + if (modes & QIODevice::Truncate) + modeList << QLatin1String("Truncate"); + if (modes & QIODevice::Text) + modeList << QLatin1String("Text"); + if (modes & QIODevice::Unbuffered) + modeList << QLatin1String("Unbuffered"); + } + qSort(modeList); + debug << modeList.join(QLatin1String("|")); + debug << ')'; + return debug; +} +#endif + +QT_END_NAMESPACE diff --git a/src/corelib/io/qiodevice.h b/src/corelib/io/qiodevice.h new file mode 100644 index 0000000000..4bfe77f616 --- /dev/null +++ b/src/corelib/io/qiodevice.h @@ -0,0 +1,254 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIODEVICE_H +#define QIODEVICE_H + +#ifndef QT_NO_QOBJECT +#include <QtCore/qobject.h> +#else +#include <QtCore/qobjectdefs.h> +#include <QtCore/qscopedpointer.h> +#endif +#include <QtCore/qstring.h> + +#ifdef open +#error qiodevice.h must be included before any header file that defines open +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QByteArray; +class QIODevicePrivate; + +class Q_CORE_EXPORT QIODevice +#ifndef QT_NO_QOBJECT + : public QObject +#endif +{ +#ifndef QT_NO_QOBJECT + Q_OBJECT +#endif +public: + enum OpenModeFlag { + NotOpen = 0x0000, + ReadOnly = 0x0001, + WriteOnly = 0x0002, + ReadWrite = ReadOnly | WriteOnly, + Append = 0x0004, + Truncate = 0x0008, + Text = 0x0010, + Unbuffered = 0x0020 + }; + Q_DECLARE_FLAGS(OpenMode, OpenModeFlag) + + QIODevice(); +#ifndef QT_NO_QOBJECT + explicit QIODevice(QObject *parent); +#endif + virtual ~QIODevice(); + + OpenMode openMode() const; + + void setTextModeEnabled(bool enabled); + bool isTextModeEnabled() const; + + bool isOpen() const; + bool isReadable() const; + bool isWritable() const; + virtual bool isSequential() const; + + virtual bool open(OpenMode mode); + virtual void close(); + + // ### Qt 5: pos() and seek() should not be virtual, and + // ### seek() should call a virtual seekData() function. + virtual qint64 pos() const; + virtual qint64 size() const; + virtual bool seek(qint64 pos); + virtual bool atEnd() const; + virtual bool reset(); + + virtual qint64 bytesAvailable() const; + virtual qint64 bytesToWrite() const; + + qint64 read(char *data, qint64 maxlen); + QByteArray read(qint64 maxlen); + QByteArray readAll(); + qint64 readLine(char *data, qint64 maxlen); + QByteArray readLine(qint64 maxlen = 0); + virtual bool canReadLine() const; + + qint64 write(const char *data, qint64 len); + qint64 write(const char *data); + inline qint64 write(const QByteArray &data) + { return write(data.constData(), data.size()); } + + qint64 peek(char *data, qint64 maxlen); + QByteArray peek(qint64 maxlen); + + virtual bool waitForReadyRead(int msecs); + virtual bool waitForBytesWritten(int msecs); + + void ungetChar(char c); + bool putChar(char c); + bool getChar(char *c); + + QString errorString() const; + +#ifndef QT_NO_QOBJECT +Q_SIGNALS: + void readyRead(); + void bytesWritten(qint64 bytes); + void aboutToClose(); + void readChannelFinished(); +#endif + +protected: +#ifdef QT_NO_QOBJECT + QIODevice(QIODevicePrivate &dd); +#else + QIODevice(QIODevicePrivate &dd, QObject *parent = 0); +#endif + virtual qint64 readData(char *data, qint64 maxlen) = 0; + virtual qint64 readLineData(char *data, qint64 maxlen); + virtual qint64 writeData(const char *data, qint64 len) = 0; + + void setOpenMode(OpenMode openMode); + + void setErrorString(const QString &errorString); + +#ifdef QT_NO_QOBJECT + QScopedPointer<QIODevicePrivate> d_ptr; +#endif + +private: + Q_DECLARE_PRIVATE(QIODevice) + Q_DISABLE_COPY(QIODevice) + +#ifdef QT3_SUPPORT +public: + typedef qint64 Offset; + + inline QT3_SUPPORT int flags() const { return static_cast<int>(openMode()); } + inline QT3_SUPPORT int mode() const { return static_cast<int>(openMode()); } + inline QT3_SUPPORT int state() const; + + inline QT3_SUPPORT bool isDirectAccess() const { return !isSequential(); } + inline QT3_SUPPORT bool isSequentialAccess() const { return isSequential(); } + inline QT3_SUPPORT bool isCombinedAccess() const { return false; } + inline QT3_SUPPORT bool isBuffered() const { return true; } + inline QT3_SUPPORT bool isRaw() const { return false; } + inline QT3_SUPPORT bool isSynchronous() const { return true; } + inline QT3_SUPPORT bool isAsynchronous() const { return false; } + inline QT3_SUPPORT bool isTranslated() const { return (openMode() & Text) != 0; } + inline QT3_SUPPORT bool isInactive() const { return !isOpen(); } + + typedef int Status; + QT3_SUPPORT Status status() const; + QT3_SUPPORT void resetStatus(); + + inline QT3_SUPPORT Offset at() const { return pos(); } + inline QT3_SUPPORT bool at(Offset offset) { return seek(offset); } + + inline QT3_SUPPORT qint64 readBlock(char *data, quint64 maxlen) { return read(data, maxlen); } + inline QT3_SUPPORT qint64 writeBlock(const char *data, quint64 len) { return write(data, len); } + inline QT3_SUPPORT qint64 writeBlock(const QByteArray &data) { return write(data); } + + inline QT3_SUPPORT int getch() { char c; return getChar(&c) ? int(uchar(c)) : -1; } + inline QT3_SUPPORT int putch(int c) { return putChar(char(c)) ? int(uchar(c)) : -1; } + inline QT3_SUPPORT int ungetch(int c) { ungetChar(uchar(c)); return c; } +#endif +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QIODevice::OpenMode) + +#ifdef QT3_SUPPORT +static QT3_SUPPORT_VARIABLE const uint IO_Direct = 0x0100; +static QT3_SUPPORT_VARIABLE const uint IO_Sequential = 0x0200; +static QT3_SUPPORT_VARIABLE const uint IO_Combined = 0x0300; +static QT3_SUPPORT_VARIABLE const uint IO_TypeMask = 0x0300; + +static QT3_SUPPORT_VARIABLE const uint IO_Raw = 0x0000; +static QT3_SUPPORT_VARIABLE const uint IO_Async = 0x0000; + +#define IO_ReadOnly QIODevice::ReadOnly +#define IO_WriteOnly QIODevice::WriteOnly +#define IO_ReadWrite QIODevice::ReadWrite +#define IO_Append QIODevice::Append +#define IO_Truncate QIODevice::Truncate +#define IO_Translate QIODevice::Text +#define IO_ModeMask 0x00ff + +static QT3_SUPPORT_VARIABLE const uint IO_Open = 0x1000; +static QT3_SUPPORT_VARIABLE const uint IO_StateMask = 0xf000; + +static QT3_SUPPORT_VARIABLE const uint IO_Ok = 0; +static QT3_SUPPORT_VARIABLE const uint IO_ReadError = 1; +static QT3_SUPPORT_VARIABLE const uint IO_WriteError = 2; +static QT3_SUPPORT_VARIABLE const uint IO_FatalError = 3; +static QT3_SUPPORT_VARIABLE const uint IO_ResourceError = 4; +static QT3_SUPPORT_VARIABLE const uint IO_OpenError = 5; +static QT3_SUPPORT_VARIABLE const uint IO_ConnectError = 5; +static QT3_SUPPORT_VARIABLE const uint IO_AbortError = 6; +static QT3_SUPPORT_VARIABLE const uint IO_TimeOutError = 7; +static QT3_SUPPORT_VARIABLE const uint IO_UnspecifiedError = 8; + +inline QT3_SUPPORT int QIODevice::state() const +{ + return isOpen() ? 0x1000 : 0; +} +#endif + +#if !defined(QT_NO_DEBUG_STREAM) +class QDebug; +Q_CORE_EXPORT QDebug operator<<(QDebug debug, QIODevice::OpenMode modes); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QIODEVICE_H diff --git a/src/corelib/io/qiodevice_p.h b/src/corelib/io/qiodevice_p.h new file mode 100644 index 0000000000..06de7d7781 --- /dev/null +++ b/src/corelib/io/qiodevice_p.h @@ -0,0 +1,245 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIODEVICE_P_H +#define QIODEVICE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of QIODevice. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qiodevice.h" +#include "QtCore/qbytearray.h" +#include "QtCore/qobjectdefs.h" +#include "QtCore/qstring.h" +#include "private/qringbuffer_p.h" +#ifndef QT_NO_QOBJECT +#include "private/qobject_p.h" +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QIODEVICE_BUFFERSIZE +#define QIODEVICE_BUFFERSIZE Q_INT64_C(16384) +#endif + +// This is QIODevice's read buffer, optimized for read(), isEmpty() and getChar() +class QIODevicePrivateLinearBuffer +{ +public: + QIODevicePrivateLinearBuffer(int) : len(0), first(0), buf(0), capacity(0) { + } + ~QIODevicePrivateLinearBuffer() { + delete [] buf; + } + void clear() { + first = buf; + len = 0; + } + int size() const { + return len; + } + bool isEmpty() const { + return len == 0; + } + void skip(int n) { + if (n >= len) { + clear(); + } else { + len -= n; + first += n; + } + } + int getChar() { + if (len == 0) + return -1; + int ch = uchar(*first); + len--; + first++; + return ch; + } + int read(char* target, int size) { + int r = qMin(size, len); + memcpy(target, first, r); + len -= r; + first += r; + return r; + } + char* reserve(int size) { + makeSpace(size + len, freeSpaceAtEnd); + char* writePtr = first + len; + len += size; + return writePtr; + } + void chop(int size) { + if (size >= len) { + clear(); + } else { + len -= size; + } + } + QByteArray readAll() { + char* f = first; + int l = len; + clear(); + return QByteArray(f, l); + } + int readLine(char* target, int size) { + int r = qMin(size, len); + char* eol = static_cast<char*>(memchr(first, '\n', r)); + if (eol) + r = 1+(eol-first); + memcpy(target, first, r); + len -= r; + first += r; + return int(r); + } + bool canReadLine() const { + return memchr(first, '\n', len); + } + void ungetChar(char c) { + if (first == buf) { + // underflow, the existing valid data needs to move to the end of the (potentially bigger) buffer + makeSpace(len+1, freeSpaceAtStart); + } + first--; + len++; + *first = c; + } + void ungetBlock(const char* block, int size) { + if ((first - buf) < size) { + // underflow, the existing valid data needs to move to the end of the (potentially bigger) buffer + makeSpace(len + size, freeSpaceAtStart); + } + first -= size; + len += size; + memcpy(first, block, size); + } + +private: + enum FreeSpacePos {freeSpaceAtStart, freeSpaceAtEnd}; + void makeSpace(size_t required, FreeSpacePos where) { + size_t newCapacity = qMax(capacity, size_t(QIODEVICE_BUFFERSIZE)); + while (newCapacity < required) + newCapacity *= 2; + int moveOffset = (where == freeSpaceAtEnd) ? 0 : newCapacity - len; + if (newCapacity > capacity) { + // allocate more space + char* newBuf = new char[newCapacity]; + memmove(newBuf + moveOffset, first, len); + delete [] buf; + buf = newBuf; + capacity = newCapacity; + } else { + // shift any existing data to make space + memmove(buf + moveOffset, first, len); + } + first = buf + moveOffset; + } + +private: + // length of the unread data + int len; + // start of the unread data + char* first; + // the allocated buffer + char* buf; + // allocated buffer size + size_t capacity; +}; + +class Q_CORE_EXPORT QIODevicePrivate +#ifndef QT_NO_QOBJECT + : public QObjectPrivate +#endif +{ + Q_DECLARE_PUBLIC(QIODevice) + +public: + QIODevicePrivate(); + virtual ~QIODevicePrivate(); + + QIODevice::OpenMode openMode; + QString errorString; + + QIODevicePrivateLinearBuffer buffer; + qint64 pos; + qint64 devicePos; + // these three are for fast position updates during read, avoiding isSequential test + qint64 seqDumpPos; + qint64 *pPos; + qint64 *pDevicePos; + bool baseReadLineDataCalled; + bool firstRead; + + virtual bool putCharHelper(char c); + + enum AccessMode { + Unset, + Sequential, + RandomAccess + }; + mutable AccessMode accessMode; + inline bool isSequential() const + { + if (accessMode == Unset) + accessMode = q_func()->isSequential() ? Sequential : RandomAccess; + return accessMode == Sequential; + } + + virtual qint64 peek(char *data, qint64 maxSize); + virtual QByteArray peek(qint64 maxSize); + +#ifdef QT_NO_QOBJECT + QIODevice *q_ptr; +#endif +}; + +QT_END_NAMESPACE + +#endif // QIODEVICE_P_H diff --git a/src/corelib/io/qnoncontiguousbytedevice.cpp b/src/corelib/io/qnoncontiguousbytedevice.cpp new file mode 100644 index 0000000000..71cf92db24 --- /dev/null +++ b/src/corelib/io/qnoncontiguousbytedevice.cpp @@ -0,0 +1,545 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnoncontiguousbytedevice_p.h" +#include <qbuffer.h> +#include <qdebug.h> +#include <qfile.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QNonContiguousByteDevice + \brief A QNonContiguousByteDevice is a representation of a + file, array or buffer that allows access with a read pointer. + \since 4.6 + + \inmodule QtCore + + The goal of this class is to have a data representation that + allows us to avoid doing a memcpy as we have to do with QIODevice. + + \sa QNonContiguousByteDeviceFactory + + \internal +*/ +/*! + \fn virtual const char* QNonContiguousByteDevice::readPointer(qint64 maximumLength, qint64 &len) + + Return a byte pointer for at most \a maximumLength bytes of that device. + if \a maximumLength is -1, the caller does not care about the length and + the device may return what it desires to. + The actual number of bytes the pointer is valid for is returned in + the \a len variable. + \a len will be -1 if EOF or an error occurs. + If it was really EOF can then afterwards be checked with atEnd() + Returns 0 if it is not possible to read at that position. + + \sa atEnd() + + \internal +*/ +/*! + \fn virtual bool QNonContiguousByteDevice::advanceReadPointer(qint64 amount) + + will advance the internal read pointer by \a amount bytes. + The old readPointer is invalid after this call. + + \sa readPointer() + + \internal +*/ +/*! + \fn virtual bool QNonContiguousByteDevice::atEnd() + + Returns true if everything has been read and the read + pointer cannot be advanced anymore. + + \sa readPointer(), advanceReadPointer(), reset() + + \internal +*/ +/*! + \fn virtual bool QNonContiguousByteDevice::reset() + + Moves the internal read pointer back to the beginning. + Returns false if this was not possible. + + \sa atEnd(), disableReset() + + \internal +*/ +/*! + \fn void QNonContiguousByteDevice::disableReset() + + Disable the reset() call, e.g. it will always + do nothing and return false. + + \sa reset() + + \internal +*/ +/*! + \fn virtual qint64 QNonContiguousByteDevice::size() + + Returns the size of the complete device or -1 if unknown. + May also return less/more than what can be actually read with readPointer() + + \internal +*/ +/*! + \fn void QNonContiguousByteDevice::readyRead() + + Emitted when there is data available + + \internal +*/ +/*! + \fn void QNonContiguousByteDevice::readProgress(qint64 current, qint64 total) + + Emitted when data has been "read" by advancing the read pointer + + \internal +*/ + +QNonContiguousByteDevice::QNonContiguousByteDevice() : QObject((QObject*)0), resetDisabled(false) +{ +} + +QNonContiguousByteDevice::~QNonContiguousByteDevice() +{ +} + +void QNonContiguousByteDevice::disableReset() +{ + resetDisabled = true; +} + +// FIXME we should scrap this whole implementation and instead change the ByteArrayImpl to be able to cope with sub-arrays? +QNonContiguousByteDeviceBufferImpl::QNonContiguousByteDeviceBufferImpl(QBuffer *b) : QNonContiguousByteDevice() +{ + buffer = b; + byteArray = QByteArray::fromRawData(buffer->buffer().constData() + buffer->pos(), buffer->size() - buffer->pos()); + arrayImpl = new QNonContiguousByteDeviceByteArrayImpl(&byteArray); + arrayImpl->setParent(this); + connect(arrayImpl, SIGNAL(readyRead()), SIGNAL(readyRead())); + connect(arrayImpl, SIGNAL(readProgress(qint64,qint64)), SIGNAL(readProgress(qint64,qint64))); +} + +QNonContiguousByteDeviceBufferImpl::~QNonContiguousByteDeviceBufferImpl() +{ +} + +const char* QNonContiguousByteDeviceBufferImpl::readPointer(qint64 maximumLength, qint64 &len) +{ + return arrayImpl->readPointer(maximumLength, len); +} + +bool QNonContiguousByteDeviceBufferImpl::advanceReadPointer(qint64 amount) +{ + return arrayImpl->advanceReadPointer(amount); +} + +bool QNonContiguousByteDeviceBufferImpl::atEnd() +{ + return arrayImpl->atEnd(); +} + +bool QNonContiguousByteDeviceBufferImpl::reset() +{ + if (resetDisabled) + return false; + return arrayImpl->reset(); +} + +qint64 QNonContiguousByteDeviceBufferImpl::size() +{ + return arrayImpl->size(); +} + +QNonContiguousByteDeviceByteArrayImpl::QNonContiguousByteDeviceByteArrayImpl(QByteArray *ba) : QNonContiguousByteDevice(), currentPosition(0) +{ + byteArray = ba; +} + +QNonContiguousByteDeviceByteArrayImpl::~QNonContiguousByteDeviceByteArrayImpl() +{ +} + +const char* QNonContiguousByteDeviceByteArrayImpl::readPointer(qint64 maximumLength, qint64 &len) +{ + if (atEnd()) { + len = -1; + return 0; + } + + if (maximumLength != -1) + len = qMin(maximumLength, size() - currentPosition); + else + len = size() - currentPosition; + + return byteArray->constData() + currentPosition; +} + +bool QNonContiguousByteDeviceByteArrayImpl::advanceReadPointer(qint64 amount) +{ + currentPosition += amount; + emit readProgress(currentPosition, size()); + return true; +} + +bool QNonContiguousByteDeviceByteArrayImpl::atEnd() +{ + return currentPosition >= size(); +} + +bool QNonContiguousByteDeviceByteArrayImpl::reset() +{ + if (resetDisabled) + return false; + + currentPosition = 0; + return true; +} + +qint64 QNonContiguousByteDeviceByteArrayImpl::size() +{ + return byteArray->size(); +} + +QNonContiguousByteDeviceRingBufferImpl::QNonContiguousByteDeviceRingBufferImpl(QSharedPointer<QRingBuffer> rb) + : QNonContiguousByteDevice(), currentPosition(0) +{ + ringBuffer = rb; +} + +QNonContiguousByteDeviceRingBufferImpl::~QNonContiguousByteDeviceRingBufferImpl() +{ +} + +const char* QNonContiguousByteDeviceRingBufferImpl::readPointer(qint64 maximumLength, qint64 &len) +{ + if (atEnd()) { + len = -1; + return 0; + } + + const char *returnValue = ringBuffer->readPointerAtPosition(currentPosition, len); + + if (maximumLength != -1) + len = qMin(len, maximumLength); + + return returnValue; +} + +bool QNonContiguousByteDeviceRingBufferImpl::advanceReadPointer(qint64 amount) +{ + currentPosition += amount; + emit readProgress(currentPosition, size()); + return true; +} + +bool QNonContiguousByteDeviceRingBufferImpl::atEnd() +{ + return currentPosition >= size(); +} + +bool QNonContiguousByteDeviceRingBufferImpl::reset() +{ + if (resetDisabled) + return false; + + currentPosition = 0; + return true; +} + +qint64 QNonContiguousByteDeviceRingBufferImpl::size() +{ + return ringBuffer->size(); +} + +QNonContiguousByteDeviceIoDeviceImpl::QNonContiguousByteDeviceIoDeviceImpl(QIODevice *d) + : QNonContiguousByteDevice(), + currentReadBuffer(0), currentReadBufferSize(16*1024), + currentReadBufferAmount(0), currentReadBufferPosition(0), totalAdvancements(0), + eof(false) +{ + device = d; + initialPosition = d->pos(); + connect(device, SIGNAL(readyRead()), this, SIGNAL(readyRead()), Qt::QueuedConnection); + connect(device, SIGNAL(readChannelFinished()), this, SIGNAL(readyRead()), Qt::QueuedConnection); +} + +QNonContiguousByteDeviceIoDeviceImpl::~QNonContiguousByteDeviceIoDeviceImpl() +{ + delete currentReadBuffer; +} + +const char* QNonContiguousByteDeviceIoDeviceImpl::readPointer(qint64 maximumLength, qint64 &len) +{ + if (eof == true) { + len = -1; + return 0; + } + + if (currentReadBuffer == 0) + currentReadBuffer = new QByteArray(currentReadBufferSize, '\0'); // lazy alloc + + if (maximumLength == -1) + maximumLength = currentReadBufferSize; + + if (currentReadBufferAmount - currentReadBufferPosition > 0) { + len = currentReadBufferAmount - currentReadBufferPosition; + return currentReadBuffer->data() + currentReadBufferPosition; + } + + qint64 haveRead = device->read(currentReadBuffer->data(), qMin(maximumLength, currentReadBufferSize)); + + if ((haveRead == -1) || (haveRead == 0 && device->atEnd() && !device->isSequential())) { + eof = true; + len = -1; + // size was unknown before, emit a readProgress with the final size + if (size() == -1) + emit readProgress(totalAdvancements, totalAdvancements); + return 0; + } + + currentReadBufferAmount = haveRead; + currentReadBufferPosition = 0; + + len = haveRead; + return currentReadBuffer->data(); +} + +bool QNonContiguousByteDeviceIoDeviceImpl::advanceReadPointer(qint64 amount) +{ + totalAdvancements += amount; + + // normal advancement + currentReadBufferPosition += amount; + + if (size() == -1) + emit readProgress(totalAdvancements, totalAdvancements); + else + emit readProgress(totalAdvancements, size()); + + // advancing over that what has actually been read before + if (currentReadBufferPosition > currentReadBufferAmount) { + qint64 i = currentReadBufferPosition - currentReadBufferAmount; + while (i > 0) { + if (device->getChar(0) == false) { + emit readProgress(totalAdvancements - i, size()); + return false; // ### FIXME handle eof + } + i--; + } + + currentReadBufferPosition = 0; + currentReadBufferAmount = 0; + } + + + return true; +} + +bool QNonContiguousByteDeviceIoDeviceImpl::atEnd() +{ + return eof == true; +} + +bool QNonContiguousByteDeviceIoDeviceImpl::reset() +{ + if (resetDisabled) + return false; + + if (device->seek(initialPosition)) { + eof = false; // assume eof is false, it will be true after a read has been attempted + return true; + } + + return false; +} + +qint64 QNonContiguousByteDeviceIoDeviceImpl::size() +{ + // note that this is different from the size() implementation of QIODevice! + + if (device->isSequential()) + return -1; + + return device->size() - initialPosition; +} + +QByteDeviceWrappingIoDevice::QByteDeviceWrappingIoDevice(QNonContiguousByteDevice *bd) : QIODevice((QObject*)0) +{ + byteDevice = bd; + connect(bd, SIGNAL(readyRead()), SIGNAL(readyRead())); + + open(ReadOnly); +} + +QByteDeviceWrappingIoDevice::~QByteDeviceWrappingIoDevice() +{ + +} + +bool QByteDeviceWrappingIoDevice::isSequential() const +{ + return (byteDevice->size() == -1); +} + +bool QByteDeviceWrappingIoDevice::atEnd() const +{ + return byteDevice->atEnd(); +} + +bool QByteDeviceWrappingIoDevice::reset() +{ + return byteDevice->reset(); +} + +qint64 QByteDeviceWrappingIoDevice::size() const +{ + if (isSequential()) + return 0; + + return byteDevice->size(); +} + + +qint64 QByteDeviceWrappingIoDevice::readData( char * data, qint64 maxSize) +{ + qint64 len; + const char *readPointer = byteDevice->readPointer(maxSize, len); + if (len == -1) + return -1; + + memcpy(data, readPointer, len); + byteDevice->advanceReadPointer(len); + return len; +} + +qint64 QByteDeviceWrappingIoDevice::writeData( const char* data, qint64 maxSize) +{ + Q_UNUSED(data); + Q_UNUSED(maxSize); + return -1; +} + +/*! + \class QNonContiguousByteDeviceFactory + \since 4.6 + + \inmodule QtCore + + Creates a QNonContiguousByteDevice out of a QIODevice, + QByteArray etc. + + \sa QNonContiguousByteDevice + + \internal +*/ + +/*! + \fn static QNonContiguousByteDevice* QNonContiguousByteDeviceFactory::create(QIODevice *device); + + Create a QNonContiguousByteDevice out of a QIODevice. + For QFile, QBuffer and all other QIoDevice, sequential or not. + + \internal +*/ +QNonContiguousByteDevice* QNonContiguousByteDeviceFactory::create(QIODevice *device) +{ + // shortcut if it is a QBuffer + if (QBuffer* buffer = qobject_cast<QBuffer*>(device)) { + return new QNonContiguousByteDeviceBufferImpl(buffer); + } + + // ### FIXME special case if device is a QFile that supports map() + // then we can actually deal with the file without using read/peek + + // generic QIODevice + return new QNonContiguousByteDeviceIoDeviceImpl(device); // FIXME +} + +/*! + \fn static QNonContiguousByteDevice* QNonContiguousByteDeviceFactory::create(QRingBuffer *ringBuffer); + + Create a QNonContiguousByteDevice out of a QRingBuffer. + + \internal +*/ +QNonContiguousByteDevice* QNonContiguousByteDeviceFactory::create(QSharedPointer<QRingBuffer> ringBuffer) +{ + return new QNonContiguousByteDeviceRingBufferImpl(ringBuffer); +} + +/*! + \fn static QNonContiguousByteDevice* QNonContiguousByteDeviceFactory::create(QByteArray *byteArray); + + Create a QNonContiguousByteDevice out of a QByteArray. + + \internal +*/ +QNonContiguousByteDevice* QNonContiguousByteDeviceFactory::create(QByteArray *byteArray) +{ + return new QNonContiguousByteDeviceByteArrayImpl(byteArray); +} + +/*! + \fn static QIODevice* QNonContiguousByteDeviceFactory::wrap(QNonContiguousByteDevice* byteDevice); + + Wrap the \a byteDevice (possibly again) into a QIODevice. + + \internal +*/ +QIODevice* QNonContiguousByteDeviceFactory::wrap(QNonContiguousByteDevice* byteDevice) +{ + // ### FIXME if it already has been based on QIoDevice, we could that one out again + // and save some calling + + // needed for FTP backend + + return new QByteDeviceWrappingIoDevice(byteDevice); +} + +QT_END_NAMESPACE + diff --git a/src/corelib/io/qnoncontiguousbytedevice_p.h b/src/corelib/io/qnoncontiguousbytedevice_p.h new file mode 100644 index 0000000000..5e0b1bbb8f --- /dev/null +++ b/src/corelib/io/qnoncontiguousbytedevice_p.h @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNONCONTIGUOUSBYTEDEVICE_H +#define QNONCONTIGUOUSBYTEDEVICE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of a number of Qt sources files. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qbuffer.h> +#include <QtCore/qiodevice.h> +#include <QtCore/QSharedPointer> +#include "private/qringbuffer_p.h" + +QT_BEGIN_NAMESPACE + +class Q_CORE_EXPORT QNonContiguousByteDevice : public QObject +{ + Q_OBJECT +public: + virtual const char* readPointer(qint64 maximumLength, qint64 &len) = 0; + virtual bool advanceReadPointer(qint64 amount) = 0; + virtual bool atEnd() = 0; + virtual bool reset() = 0; + void disableReset(); + bool isResetDisabled() { return resetDisabled; } + virtual qint64 size() = 0; + + virtual ~QNonContiguousByteDevice(); + +protected: + QNonContiguousByteDevice(); + + + bool resetDisabled; +Q_SIGNALS: + void readyRead(); + void readProgress(qint64 current, qint64 total); +}; + +class Q_CORE_EXPORT QNonContiguousByteDeviceFactory +{ +public: + static QNonContiguousByteDevice* create(QIODevice *device); + static QNonContiguousByteDevice* create(QByteArray *byteArray); + static QNonContiguousByteDevice* create(QSharedPointer<QRingBuffer> ringBuffer); + static QIODevice* wrap(QNonContiguousByteDevice* byteDevice); +}; + +// the actual implementations +// + +class QNonContiguousByteDeviceByteArrayImpl : public QNonContiguousByteDevice +{ +public: + QNonContiguousByteDeviceByteArrayImpl(QByteArray *ba); + ~QNonContiguousByteDeviceByteArrayImpl(); + const char* readPointer(qint64 maximumLength, qint64 &len); + bool advanceReadPointer(qint64 amount); + bool atEnd(); + bool reset(); + qint64 size(); +protected: + QByteArray* byteArray; + qint64 currentPosition; +}; + +class QNonContiguousByteDeviceRingBufferImpl : public QNonContiguousByteDevice +{ +public: + QNonContiguousByteDeviceRingBufferImpl(QSharedPointer<QRingBuffer> rb); + ~QNonContiguousByteDeviceRingBufferImpl(); + const char* readPointer(qint64 maximumLength, qint64 &len); + bool advanceReadPointer(qint64 amount); + bool atEnd(); + bool reset(); + qint64 size(); +protected: + QSharedPointer<QRingBuffer> ringBuffer; + qint64 currentPosition; +}; + + +class QNonContiguousByteDeviceIoDeviceImpl : public QNonContiguousByteDevice +{ + Q_OBJECT +public: + QNonContiguousByteDeviceIoDeviceImpl(QIODevice *d); + ~QNonContiguousByteDeviceIoDeviceImpl(); + const char* readPointer(qint64 maximumLength, qint64 &len); + bool advanceReadPointer(qint64 amount); + bool atEnd(); + bool reset(); + qint64 size(); +protected: + QIODevice* device; + QByteArray* currentReadBuffer; + qint64 currentReadBufferSize; + qint64 currentReadBufferAmount; + qint64 currentReadBufferPosition; + qint64 totalAdvancements; + bool eof; + qint64 initialPosition; +}; + +class QNonContiguousByteDeviceBufferImpl : public QNonContiguousByteDevice +{ + Q_OBJECT +public: + QNonContiguousByteDeviceBufferImpl(QBuffer *b); + ~QNonContiguousByteDeviceBufferImpl(); + const char* readPointer(qint64 maximumLength, qint64 &len); + bool advanceReadPointer(qint64 amount); + bool atEnd(); + bool reset(); + qint64 size(); +protected: + QBuffer* buffer; + QByteArray byteArray; + QNonContiguousByteDeviceByteArrayImpl* arrayImpl; +}; + +// ... and the reverse thing +class QByteDeviceWrappingIoDevice : public QIODevice +{ +public: + QByteDeviceWrappingIoDevice (QNonContiguousByteDevice *bd); + ~QByteDeviceWrappingIoDevice (); + virtual bool isSequential () const; + virtual bool atEnd () const; + virtual bool reset (); + virtual qint64 size () const; +protected: + virtual qint64 readData ( char * data, qint64 maxSize ); + virtual qint64 writeData ( const char * data, qint64 maxSize ); + + QNonContiguousByteDevice *byteDevice; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp new file mode 100644 index 0000000000..a45225f3f7 --- /dev/null +++ b/src/corelib/io/qprocess.cpp @@ -0,0 +1,2371 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QPROCESS_DEBUG + +#if defined QPROCESS_DEBUG +#include <qdebug.h> +#include <qstring.h> +#include <ctype.h> +#if !defined(Q_OS_WINCE) +#include <errno.h> +#endif + +QT_BEGIN_NAMESPACE +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len && i < maxSize; ++i) { + char c = data[i]; + if (isprint(c)) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + char buf[5]; + qsnprintf(buf, sizeof(buf), "\\%3o", c); + buf[4] = '\0'; + out += QByteArray(buf); + } + } + + if (len < maxSize) + out += "..."; + + return out; +} + +QT_END_NAMESPACE + +#endif + +#include "qprocess.h" +#include "qprocess_p.h" + +#include <qbytearray.h> +#include <qelapsedtimer.h> +#include <qcoreapplication.h> +#include <qsocketnotifier.h> +#include <qtimer.h> + +#ifdef Q_WS_WIN +#include <private/qwineventnotifier_p.h> +#endif + +#ifdef Q_OS_SYMBIAN +#include <e32std.h> +#endif + +#ifndef QT_NO_PROCESS + +QT_BEGIN_NAMESPACE + +/*! + \class QProcessEnvironment + + \brief The QProcessEnvironment class holds the environment variables that + can be passed to a program. + + \ingroup io + \ingroup misc + \mainclass + \reentrant + \since 4.6 + + A process's environment is composed of a set of key=value pairs known as + environment variables. The QProcessEnvironment class wraps that concept + and allows easy manipulation of those variables. It's meant to be used + along with QProcess, to set the environment for child processes. It + cannot be used to change the current process's environment. + + The environment of the calling process can be obtained using + QProcessEnvironment::systemEnvironment(). + + On Unix systems, the variable names are case-sensitive. For that reason, + this class will not touch the names of the variables. Note as well that + Unix environment allows both variable names and contents to contain arbitrary + binary data (except for the NUL character), but this is not supported by + QProcessEnvironment. This class only supports names and values that are + encodable by the current locale settings (see QTextCodec::codecForLocale). + + On Windows, the variable names are case-insensitive. Therefore, + QProcessEnvironment will always uppercase the names and do case-insensitive + comparisons. + + On Windows CE, the concept of environment does not exist. This class will + keep the values set for compatibility with other platforms, but the values + set will have no effect on the processes being created. + + \sa QProcess, QProcess::systemEnvironment(), QProcess::setProcessEnvironment() +*/ +#ifdef Q_OS_WIN +static inline QProcessEnvironmentPrivate::Unit prepareName(const QString &name) +{ return name.toUpper(); } +static inline QProcessEnvironmentPrivate::Unit prepareName(const QByteArray &name) +{ return QString::fromLocal8Bit(name).toUpper(); } +static inline QString nameToString(const QProcessEnvironmentPrivate::Unit &name) +{ return name; } +static inline QProcessEnvironmentPrivate::Unit prepareValue(const QString &value) +{ return value; } +static inline QProcessEnvironmentPrivate::Unit prepareValue(const QByteArray &value) +{ return QString::fromLocal8Bit(value); } +static inline QString valueToString(const QProcessEnvironmentPrivate::Unit &value) +{ return value; } +static inline QByteArray valueToByteArray(const QProcessEnvironmentPrivate::Unit &value) +{ return value.toLocal8Bit(); } +#else +static inline QProcessEnvironmentPrivate::Unit prepareName(const QByteArray &name) +{ return name; } +static inline QProcessEnvironmentPrivate::Unit prepareName(const QString &name) +{ return name.toLocal8Bit(); } +static inline QString nameToString(const QProcessEnvironmentPrivate::Unit &name) +{ return QString::fromLocal8Bit(name); } +static inline QProcessEnvironmentPrivate::Unit prepareValue(const QByteArray &value) +{ return value; } +static inline QProcessEnvironmentPrivate::Unit prepareValue(const QString &value) +{ return value.toLocal8Bit(); } +static inline QString valueToString(const QProcessEnvironmentPrivate::Unit &value) +{ return QString::fromLocal8Bit(value); } +static inline QByteArray valueToByteArray(const QProcessEnvironmentPrivate::Unit &value) +{ return value; } +#endif + +template<> void QSharedDataPointer<QProcessEnvironmentPrivate>::detach() +{ + if (d && d->ref == 1) + return; + QProcessEnvironmentPrivate *x = (d ? new QProcessEnvironmentPrivate(*d) + : new QProcessEnvironmentPrivate); + x->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = x; +} + +QStringList QProcessEnvironmentPrivate::toList() const +{ + QStringList result; + QHash<Unit, Unit>::ConstIterator it = hash.constBegin(), + end = hash.constEnd(); + for ( ; it != end; ++it) { + QString data = nameToString(it.key()); + QString value = valueToString(it.value()); + data.reserve(data.length() + value.length() + 1); + data.append(QLatin1Char('=')); + data.append(value); + result << data; + } + return result; +} + +QProcessEnvironment QProcessEnvironmentPrivate::fromList(const QStringList &list) +{ + QProcessEnvironment env; + QStringList::ConstIterator it = list.constBegin(), + end = list.constEnd(); + for ( ; it != end; ++it) { + int pos = it->indexOf(QLatin1Char('=')); + if (pos < 1) + continue; + + QString value = it->mid(pos + 1); + QString name = *it; + name.truncate(pos); + env.insert(name, value); + } + return env; +} + +QStringList QProcessEnvironmentPrivate::keys() const +{ + QStringList result; + QHash<Unit, Unit>::ConstIterator it = hash.constBegin(), + end = hash.constEnd(); + for ( ; it != end; ++it) + result << nameToString(it.key()); + return result; +} + +void QProcessEnvironmentPrivate::insert(const Hash &h) +{ + QHash<Unit, Unit>::ConstIterator it = h.constBegin(), + end = h.constEnd(); + for ( ; it != end; ++it) + hash.insert(it.key(), it.value()); +} + +/*! + Creates a new QProcessEnvironment object. This constructor creates an + empty environment. If set on a QProcess, this will cause the current + environment variables to be removed. +*/ +QProcessEnvironment::QProcessEnvironment() + : d(0) +{ +} + +/*! + Frees the resources associated with this QProcessEnvironment object. +*/ +QProcessEnvironment::~QProcessEnvironment() +{ +} + +/*! + Creates a QProcessEnvironment object that is a copy of \a other. +*/ +QProcessEnvironment::QProcessEnvironment(const QProcessEnvironment &other) + : d(other.d) +{ +} + +/*! + Copies the contents of the \a other QProcessEnvironment object into this + one. +*/ +QProcessEnvironment &QProcessEnvironment::operator=(const QProcessEnvironment &other) +{ + d = other.d; + return *this; +} + +/*! + \fn bool QProcessEnvironment::operator !=(const QProcessEnvironment &other) const + + Returns true if this and the \a other QProcessEnvironment objects are different. + + \sa operator==() +*/ + +/*! + Returns true if this and the \a other QProcessEnvironment objects are equal. + + Two QProcessEnvironment objects are considered equal if they have the same + set of key=value pairs. The comparison of keys is done case-sensitive on + platforms where the environment is case-sensitive. + + \sa operator!=(), contains() +*/ +bool QProcessEnvironment::operator==(const QProcessEnvironment &other) const +{ + return d == other.d || (d && other.d && d->hash == other.d->hash); +} + +/*! + Returns true if this QProcessEnvironment object is empty: that is + there are no key=value pairs set. + + \sa clear(), systemEnvironment(), insert() +*/ +bool QProcessEnvironment::isEmpty() const +{ + return d ? d->hash.isEmpty() : true; +} + +/*! + Removes all key=value pairs from this QProcessEnvironment object, making + it empty. + + \sa isEmpty(), systemEnvironment() +*/ +void QProcessEnvironment::clear() +{ + if (d) + d->hash.clear(); +} + +/*! + Returns true if the environment variable of name \a name is found in + this QProcessEnvironment object. + + On Windows, variable names are case-insensitive, so the key is converted + to uppercase before searching. On other systems, names are case-sensitive + so no trasformation is applied. + + \sa insert(), value() +*/ +bool QProcessEnvironment::contains(const QString &name) const +{ + return d ? d->hash.contains(prepareName(name)) : false; +} + +/*! + Inserts the environment variable of name \a name and contents \a value + into this QProcessEnvironment object. If that variable already existed, + it is replaced by the new value. + + On Windows, variable names are case-insensitive, so this function always + uppercases the variable name before inserting. On other systems, names + are case-sensitive, so no transformation is applied. + + On most systems, inserting a variable with no contents will have the + same effect for applications as if the variable had not been set at all. + However, to guarantee that there are no incompatibilities, to remove a + variable, please use the remove() function. + + \sa contains(), remove(), value() +*/ +void QProcessEnvironment::insert(const QString &name, const QString &value) +{ + // d detaches from null + d->hash.insert(prepareName(name), prepareValue(value)); +} + +/*! + Removes the environment variable identified by \a name from this + QProcessEnvironment object. If that variable did not exist before, + nothing happens. + + On Windows, variable names are case-insensitive, so the key is converted + to uppercase before searching. On other systems, names are case-sensitive + so no trasformation is applied. + + \sa contains(), insert(), value() +*/ +void QProcessEnvironment::remove(const QString &name) +{ + if (d) + d->hash.remove(prepareName(name)); +} + +/*! + Searches this QProcessEnvironment object for a variable identified by + \a name and returns its value. If the variable is not found in this object, + then \a defaultValue is returned instead. + + On Windows, variable names are case-insensitive, so the key is converted + to uppercase before searching. On other systems, names are case-sensitive + so no trasformation is applied. + + \sa contains(), insert(), remove() +*/ +QString QProcessEnvironment::value(const QString &name, const QString &defaultValue) const +{ + if (!d) + return defaultValue; + + QProcessEnvironmentPrivate::Hash::ConstIterator it = d->hash.constFind(prepareName(name)); + if (it == d->hash.constEnd()) + return defaultValue; + + return valueToString(it.value()); +} + +/*! + Converts this QProcessEnvironment object into a list of strings, one for + each environment variable that is set. The environment variable's name + and its value are separated by an equal character ('='). + + The QStringList contents returned by this function are suitable for use + with the QProcess::setEnvironment function. However, it is recommended + to use QProcess::setProcessEnvironment instead since that will avoid + unnecessary copying of the data. + + \sa systemEnvironment(), QProcess::systemEnvironment(), QProcess::environment(), + QProcess::setEnvironment() +*/ +QStringList QProcessEnvironment::toStringList() const +{ + return d ? d->toList() : QStringList(); +} + +/*! + \since 4.8 + + Returns a list containing all the variable names in this QProcessEnvironment + object. +*/ +QStringList QProcessEnvironment::keys() const +{ + return d ? d->keys() : QStringList(); +} + +/*! + \overload + \since 4.8 + + Inserts the contents of \a e in this QProcessEnvironment object. Variables in + this object that also exist in \a e will be overwritten. +*/ +void QProcessEnvironment::insert(const QProcessEnvironment &e) +{ + if (!e.d) + return; + + // d detaches from null + d->insert(e.d->hash); +} + +void QProcessPrivate::Channel::clear() +{ + switch (type) { + case PipeSource: + Q_ASSERT(process); + process->stdinChannel.type = Normal; + process->stdinChannel.process = 0; + break; + case PipeSink: + Q_ASSERT(process); + process->stdoutChannel.type = Normal; + process->stdoutChannel.process = 0; + break; + } + + type = Normal; + file.clear(); + process = 0; +} + +/*! \fn bool QProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid) + +\internal + */ + +/*! + \class QProcess + + \brief The QProcess class is used to start external programs and + to communicate with them. + + \ingroup io + + \reentrant + + \section1 Running a Process + + To start a process, pass the name and command line arguments of + the program you want to run as arguments to start(). Arguments + are supplied as individual strings in a QStringList. + + For example, the following code snippet runs the analog clock + example in the Motif style on X11 platforms by passing strings + containing "-style" and "motif" as two items in the list of + arguments: + + \snippet doc/src/snippets/qprocess/qprocess-simpleexecution.cpp 0 + \dots + \snippet doc/src/snippets/qprocess/qprocess-simpleexecution.cpp 1 + \snippet doc/src/snippets/qprocess/qprocess-simpleexecution.cpp 2 + + QProcess then enters the \l Starting state, and when the program + has started, QProcess enters the \l Running state and emits + started(). + + QProcess allows you to treat a process as a sequential I/O + device. You can write to and read from the process just as you + would access a network connection using QTcpSocket. You can then + write to the process's standard input by calling write(), and + read the standard output by calling read(), readLine(), and + getChar(). Because it inherits QIODevice, QProcess can also be + used as an input source for QXmlReader, or for generating data to + be uploaded using QFtp. + + \note On Windows CE and Symbian, reading and writing to a process + is not supported. + + When the process exits, QProcess reenters the \l NotRunning state + (the initial state), and emits finished(). + + The finished() signal provides the exit code and exit status of + the process as arguments, and you can also call exitCode() to + obtain the exit code of the last process that finished, and + exitStatus() to obtain its exit status. If an error occurs at + any point in time, QProcess will emit the error() signal. You + can also call error() to find the type of error that occurred + last, and state() to find the current process state. + + \section1 Communicating via Channels + + Processes have two predefined output channels: The standard + output channel (\c stdout) supplies regular console output, and + the standard error channel (\c stderr) usually supplies the + errors that are printed by the process. These channels represent + two separate streams of data. You can toggle between them by + calling setReadChannel(). QProcess emits readyRead() when data is + available on the current read channel. It also emits + readyReadStandardOutput() when new standard output data is + available, and when new standard error data is available, + readyReadStandardError() is emitted. Instead of calling read(), + readLine(), or getChar(), you can explicitly read all data from + either of the two channels by calling readAllStandardOutput() or + readAllStandardError(). + + The terminology for the channels can be misleading. Be aware that + the process's output channels correspond to QProcess's + \e read channels, whereas the process's input channels correspond + to QProcess's \e write channels. This is because what we read + using QProcess is the process's output, and what we write becomes + the process's input. + + QProcess can merge the two output channels, so that standard + output and standard error data from the running process both use + the standard output channel. Call setProcessChannelMode() with + MergedChannels before starting the process to activative + this feature. You also have the option of forwarding the output of + the running process to the calling, main process, by passing + ForwardedChannels as the argument. + + Certain processes need special environment settings in order to + operate. You can set environment variables for your process by + calling setEnvironment(). To set a working directory, call + setWorkingDirectory(). By default, processes are run in the + current working directory of the calling process. + + \note On Symbian, setting environment or working directory + is not supported. The working directory will always be the private + directory of the running process. + + \section1 Synchronous Process API + + QProcess provides a set of functions which allow it to be used + without an event loop, by suspending the calling thread until + certain signals are emitted: + + \list + \o waitForStarted() blocks until the process has started. + + \o waitForReadyRead() blocks until new data is + available for reading on the current read channel. + + \o waitForBytesWritten() blocks until one payload of + data has been written to the process. + + \o waitForFinished() blocks until the process has finished. + \endlist + + Calling these functions from the main thread (the thread that + calls QApplication::exec()) may cause your user interface to + freeze. + + The following example runs \c gzip to compress the string "Qt + rocks!", without an event loop: + + \snippet doc/src/snippets/process/process.cpp 0 + + \section1 Notes for Windows Users + + Some Windows commands (for example, \c dir) are not provided by + separate applications, but by the command interpreter itself. + If you attempt to use QProcess to execute these commands directly, + it won't work. One possible solution is to execute the command + interpreter itself (\c{cmd.exe} on some Windows systems), and ask + the interpreter to execute the desired command. + + \section1 Symbian Platform Security Requirements + + On Symbian, processes which use the functions kill() or terminate() + must have the \c PowerMgmt platform security capability. If the client + process lacks this capability, these functions will fail. + + Platform security capabilities are added via the + \l{qmake-variable-reference.html#target-capability}{TARGET.CAPABILITY} + qmake variable. + + \sa QBuffer, QFile, QTcpSocket +*/ + +/*! + \enum QProcess::ProcessChannel + + This enum describes the process channels used by the running process. + Pass one of these values to setReadChannel() to set the + current read channel of QProcess. + + \value StandardOutput The standard output (stdout) of the running + process. + + \value StandardError The standard error (stderr) of the running + process. + + \sa setReadChannel() +*/ + +/*! + \enum QProcess::ProcessChannelMode + + This enum describes the process channel modes of QProcess. Pass + one of these values to setProcessChannelMode() to set the + current read channel mode. + + \value SeparateChannels QProcess manages the output of the + running process, keeping standard output and standard error data + in separate internal buffers. You can select the QProcess's + current read channel by calling setReadChannel(). This is the + default channel mode of QProcess. + + \value MergedChannels QProcess merges the output of the running + process into the standard output channel (\c stdout). The + standard error channel (\c stderr) will not receive any data. The + standard output and standard error data of the running process + are interleaved. + + \value ForwardedChannels QProcess forwards the output of the + running process onto the main process. Anything the child process + writes to its standard output and standard error will be written + to the standard output and standard error of the main process. + + \sa setProcessChannelMode() +*/ + +/*! + \enum QProcess::ProcessError + + This enum describes the different types of errors that are + reported by QProcess. + + \value FailedToStart The process failed to start. Either the + invoked program is missing, or you may have insufficient + permissions to invoke the program. + + \value Crashed The process crashed some time after starting + successfully. + + \value Timedout The last waitFor...() function timed out. The + state of QProcess is unchanged, and you can try calling + waitFor...() again. + + \value WriteError An error occurred when attempting to write to the + process. For example, the process may not be running, or it may + have closed its input channel. + + \value ReadError An error occurred when attempting to read from + the process. For example, the process may not be running. + + \value UnknownError An unknown error occurred. This is the default + return value of error(). + + \sa error() +*/ + +/*! + \enum QProcess::ProcessState + + This enum describes the different states of QProcess. + + \value NotRunning The process is not running. + + \value Starting The process is starting, but the program has not + yet been invoked. + + \value Running The process is running and is ready for reading and + writing. + + \sa state() +*/ + +/*! + \enum QProcess::ExitStatus + + This enum describes the different exit statuses of QProcess. + + \value NormalExit The process exited normally. + + \value CrashExit The process crashed. + + \sa exitStatus() +*/ + +/*! + \fn void QProcess::error(QProcess::ProcessError error) + + This signal is emitted when an error occurs with the process. The + specified \a error describes the type of error that occurred. +*/ + +/*! + \fn void QProcess::started() + + This signal is emitted by QProcess when the process has started, + and state() returns \l Running. +*/ + +/*! + \fn void QProcess::stateChanged(QProcess::ProcessState newState) + + This signal is emitted whenever the state of QProcess changes. The + \a newState argument is the state QProcess changed to. +*/ + +/*! + \fn void QProcess::finished(int exitCode) + \obsolete + \overload + + Use finished(int exitCode, QProcess::ExitStatus status) instead. +*/ + +/*! + \fn void QProcess::finished(int exitCode, QProcess::ExitStatus exitStatus) + + This signal is emitted when the process finishes. \a exitCode is the exit + code of the process, and \a exitStatus is the exit status. After the + process has finished, the buffers in QProcess are still intact. You can + still read any data that the process may have written before it finished. + + \sa exitStatus() +*/ + +/*! + \fn void QProcess::readyReadStandardOutput() + + This signal is emitted when the process has made new data + available through its standard output channel (\c stdout). It is + emitted regardless of the current \l{readChannel()}{read channel}. + + \sa readAllStandardOutput(), readChannel() +*/ + +/*! + \fn void QProcess::readyReadStandardError() + + This signal is emitted when the process has made new data + available through its standard error channel (\c stderr). It is + emitted regardless of the current \l{readChannel()}{read + channel}. + + \sa readAllStandardError(), readChannel() +*/ + +/*! \internal +*/ +QProcessPrivate::QProcessPrivate() +{ + processChannel = QProcess::StandardOutput; + processChannelMode = QProcess::SeparateChannels; + processError = QProcess::UnknownError; + processState = QProcess::NotRunning; + pid = 0; + sequenceNumber = 0; + exitCode = 0; + exitStatus = QProcess::NormalExit; + startupSocketNotifier = 0; + deathNotifier = 0; + notifier = 0; + pipeWriter = 0; + childStartedPipe[0] = INVALID_Q_PIPE; + childStartedPipe[1] = INVALID_Q_PIPE; + deathPipe[0] = INVALID_Q_PIPE; + deathPipe[1] = INVALID_Q_PIPE; + exitCode = 0; + crashed = false; + dying = false; + emittedReadyRead = false; + emittedBytesWritten = false; +#ifdef Q_WS_WIN + pipeWriter = 0; + processFinishedNotifier = 0; +#endif // Q_WS_WIN +#ifdef Q_OS_UNIX + serial = 0; +#endif +#ifdef Q_OS_SYMBIAN + symbianProcess = NULL; + processLaunched = false; +#endif +} + +/*! \internal +*/ +QProcessPrivate::~QProcessPrivate() +{ + if (stdinChannel.process) + stdinChannel.process->stdoutChannel.clear(); + if (stdoutChannel.process) + stdoutChannel.process->stdinChannel.clear(); +} + +/*! \internal +*/ +void QProcessPrivate::cleanup() +{ + q_func()->setProcessState(QProcess::NotRunning); +#ifdef Q_OS_WIN + if (pid) { + CloseHandle(pid->hThread); + CloseHandle(pid->hProcess); + delete pid; + pid = 0; + } + if (processFinishedNotifier) { + processFinishedNotifier->setEnabled(false); + qDeleteInEventHandler(processFinishedNotifier); + processFinishedNotifier = 0; + } + +#endif + pid = 0; + sequenceNumber = 0; + dying = false; + + if (stdoutChannel.notifier) { + stdoutChannel.notifier->setEnabled(false); + qDeleteInEventHandler(stdoutChannel.notifier); + stdoutChannel.notifier = 0; + } + if (stderrChannel.notifier) { + stderrChannel.notifier->setEnabled(false); + qDeleteInEventHandler(stderrChannel.notifier); + stderrChannel.notifier = 0; + } + if (stdinChannel.notifier) { + stdinChannel.notifier->setEnabled(false); + qDeleteInEventHandler(stdinChannel.notifier); + stdinChannel.notifier = 0; + } + if (startupSocketNotifier) { + startupSocketNotifier->setEnabled(false); + qDeleteInEventHandler(startupSocketNotifier); + startupSocketNotifier = 0; + } + if (deathNotifier) { + deathNotifier->setEnabled(false); + qDeleteInEventHandler(deathNotifier); + deathNotifier = 0; + } + if (notifier) { + qDeleteInEventHandler(notifier); + notifier = 0; + } + destroyPipe(stdoutChannel.pipe); + destroyPipe(stderrChannel.pipe); + destroyPipe(stdinChannel.pipe); + destroyPipe(childStartedPipe); + destroyPipe(deathPipe); +#ifdef Q_OS_UNIX + serial = 0; +#endif +#ifdef Q_OS_SYMBIAN + if (symbianProcess) { + symbianProcess->Close(); + delete symbianProcess; + symbianProcess = NULL; + } +#endif +} + +/*! \internal +*/ +bool QProcessPrivate::_q_canReadStandardOutput() +{ + Q_Q(QProcess); + qint64 available = bytesAvailableFromStdout(); + if (available == 0) { + if (stdoutChannel.notifier) + stdoutChannel.notifier->setEnabled(false); + destroyPipe(stdoutChannel.pipe); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::canReadStandardOutput(), 0 bytes available"); +#endif + return false; + } + + char *ptr = outputReadBuffer.reserve(available); + qint64 readBytes = readFromStdout(ptr, available); + if (readBytes == -1) { + processError = QProcess::ReadError; + q->setErrorString(QProcess::tr("Error reading from process")); + emit q->error(processError); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::canReadStandardOutput(), failed to read from the process"); +#endif + return false; + } +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::canReadStandardOutput(), read %d bytes from the process' output", + int(readBytes)); +#endif + + if (stdoutChannel.closed) { + outputReadBuffer.chop(readBytes); + return false; + } + + outputReadBuffer.chop(available - readBytes); + + bool didRead = false; + if (readBytes == 0) { + if (stdoutChannel.notifier) + stdoutChannel.notifier->setEnabled(false); + } else if (processChannel == QProcess::StandardOutput) { + didRead = true; + if (!emittedReadyRead) { + emittedReadyRead = true; + emit q->readyRead(); + emittedReadyRead = false; + } + } + emit q->readyReadStandardOutput(); + return didRead; +} + +/*! \internal +*/ +bool QProcessPrivate::_q_canReadStandardError() +{ + Q_Q(QProcess); + qint64 available = bytesAvailableFromStderr(); + if (available == 0) { + if (stderrChannel.notifier) + stderrChannel.notifier->setEnabled(false); + destroyPipe(stderrChannel.pipe); + return false; + } + + char *ptr = errorReadBuffer.reserve(available); + qint64 readBytes = readFromStderr(ptr, available); + if (readBytes == -1) { + processError = QProcess::ReadError; + q->setErrorString(QProcess::tr("Error reading from process")); + emit q->error(processError); + return false; + } + if (stderrChannel.closed) { + errorReadBuffer.chop(readBytes); + return false; + } + + errorReadBuffer.chop(available - readBytes); + + bool didRead = false; + if (readBytes == 0) { + if (stderrChannel.notifier) + stderrChannel.notifier->setEnabled(false); + } else if (processChannel == QProcess::StandardError) { + didRead = true; + if (!emittedReadyRead) { + emittedReadyRead = true; + emit q->readyRead(); + emittedReadyRead = false; + } + } + emit q->readyReadStandardError(); + return didRead; +} + +/*! \internal +*/ +bool QProcessPrivate::_q_canWrite() +{ + Q_Q(QProcess); + if (stdinChannel.notifier) + stdinChannel.notifier->setEnabled(false); + + if (writeBuffer.isEmpty()) { +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::canWrite(), not writing anything (empty write buffer)."); +#endif + return false; + } + + qint64 written = writeToStdin(writeBuffer.readPointer(), + writeBuffer.nextDataBlockSize()); + if (written < 0) { + destroyPipe(stdinChannel.pipe); + processError = QProcess::WriteError; + q->setErrorString(QProcess::tr("Error writing to process")); + emit q->error(processError); + return false; + } + +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::canWrite(), wrote %d bytes to the process input", int(written)); +#endif + + if (written != 0) { + writeBuffer.free(written); + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(written); + emittedBytesWritten = false; + } + } + if (stdinChannel.notifier && !writeBuffer.isEmpty()) + stdinChannel.notifier->setEnabled(true); + if (writeBuffer.isEmpty() && stdinChannel.closed) + closeWriteChannel(); + return true; +} + +/*! \internal +*/ +bool QProcessPrivate::_q_processDied() +{ + Q_Q(QProcess); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::_q_processDied()"); +#endif +#ifdef Q_OS_UNIX + if (!waitForDeadChild()) + return false; +#endif +#ifdef Q_OS_WIN + if (processFinishedNotifier) + processFinishedNotifier->setEnabled(false); +#endif + + // the process may have died before it got a chance to report that it was + // either running or stopped, so we will call _q_startupNotification() and + // give it a chance to emit started() or error(FailedToStart). + if (processState == QProcess::Starting) { + if (!_q_startupNotification()) + return true; + } + + if (dying) { + // at this point we know the process is dead. prevent + // reentering this slot recursively by calling waitForFinished() + // or opening a dialog inside slots connected to the readyRead + // signals emitted below. + return true; + } + dying = true; + + // in case there is data in the pipe line and this slot by chance + // got called before the read notifications, call these two slots + // so the data is made available before the process dies. + _q_canReadStandardOutput(); + _q_canReadStandardError(); + + findExitCode(); + + if (crashed) { + exitStatus = QProcess::CrashExit; + processError = QProcess::Crashed; + q->setErrorString(QProcess::tr("Process crashed")); + emit q->error(processError); + } + + bool wasRunning = (processState == QProcess::Running); + + cleanup(); + + if (wasRunning) { + // we received EOF now: + emit q->readChannelFinished(); + // in the future: + //emit q->standardOutputClosed(); + //emit q->standardErrorClosed(); + + emit q->finished(exitCode); + emit q->finished(exitCode, exitStatus); + } +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::_q_processDied() process is dead"); +#endif + return true; +} + +/*! \internal +*/ +bool QProcessPrivate::_q_startupNotification() +{ + Q_Q(QProcess); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::startupNotification()"); +#endif + + if (startupSocketNotifier) + startupSocketNotifier->setEnabled(false); + if (processStarted()) { + q->setProcessState(QProcess::Running); + emit q->started(); + return true; + } + + q->setProcessState(QProcess::NotRunning); + processError = QProcess::FailedToStart; + emit q->error(processError); +#ifdef Q_OS_UNIX + // make sure the process manager removes this entry + waitForDeadChild(); + findExitCode(); +#endif + cleanup(); + return false; +} + +/*! \internal +*/ +void QProcessPrivate::closeWriteChannel() +{ +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::closeWriteChannel()"); +#endif + if (stdinChannel.notifier) { + extern void qDeleteInEventHandler(QObject *o); + stdinChannel.notifier->setEnabled(false); + if (stdinChannel.notifier) { + qDeleteInEventHandler(stdinChannel.notifier); + stdinChannel.notifier = 0; + } + } +#ifdef Q_OS_WIN + // ### Find a better fix, feeding the process little by little + // instead. + flushPipeWriter(); +#endif + destroyPipe(stdinChannel.pipe); +} + +/*! + Constructs a QProcess object with the given \a parent. +*/ +QProcess::QProcess(QObject *parent) + : QIODevice(*new QProcessPrivate, parent) +{ +#if defined QPROCESS_DEBUG + qDebug("QProcess::QProcess(%p)", parent); +#endif +} + +/*! + Destructs the QProcess object, i.e., killing the process. + + Note that this function will not return until the process is + terminated. +*/ +QProcess::~QProcess() +{ + Q_D(QProcess); + if (d->processState != NotRunning) { + qWarning("QProcess: Destroyed while process is still running."); + kill(); + waitForFinished(); + } +#ifdef Q_OS_UNIX + // make sure the process manager removes this entry + d->findExitCode(); +#endif + d->cleanup(); +} + +/*! + \obsolete + Returns the read channel mode of the QProcess. This function is + equivalent to processChannelMode() + + \sa processChannelMode() +*/ +QProcess::ProcessChannelMode QProcess::readChannelMode() const +{ + return processChannelMode(); +} + +/*! + \obsolete + + Use setProcessChannelMode(\a mode) instead. + + \sa setProcessChannelMode() +*/ +void QProcess::setReadChannelMode(ProcessChannelMode mode) +{ + setProcessChannelMode(mode); +} + +/*! + \since 4.2 + + Returns the channel mode of the QProcess standard output and + standard error channels. + + \sa setProcessChannelMode(), ProcessChannelMode, setReadChannel() +*/ +QProcess::ProcessChannelMode QProcess::processChannelMode() const +{ + Q_D(const QProcess); + return d->processChannelMode; +} + +/*! + \since 4.2 + + Sets the channel mode of the QProcess standard output and standard + error channels to the \a mode specified. + This mode will be used the next time start() is called. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 0 + + \sa processChannelMode(), ProcessChannelMode, setReadChannel() +*/ +void QProcess::setProcessChannelMode(ProcessChannelMode mode) +{ + Q_D(QProcess); + d->processChannelMode = mode; +} + +/*! + Returns the current read channel of the QProcess. + + \sa setReadChannel() +*/ +QProcess::ProcessChannel QProcess::readChannel() const +{ + Q_D(const QProcess); + return d->processChannel; +} + +/*! + Sets the current read channel of the QProcess to the given \a + channel. The current input channel is used by the functions + read(), readAll(), readLine(), and getChar(). It also determines + which channel triggers QProcess to emit readyRead(). + + \sa readChannel() +*/ +void QProcess::setReadChannel(ProcessChannel channel) +{ + Q_D(QProcess); + if (d->processChannel != channel) { + QByteArray buf = d->buffer.readAll(); + if (d->processChannel == QProcess::StandardOutput) { + for (int i = buf.size() - 1; i >= 0; --i) + d->outputReadBuffer.ungetChar(buf.at(i)); + } else { + for (int i = buf.size() - 1; i >= 0; --i) + d->errorReadBuffer.ungetChar(buf.at(i)); + } + } + d->processChannel = channel; +} + +/*! + Closes the read channel \a channel. After calling this function, + QProcess will no longer receive data on the channel. Any data that + has already been received is still available for reading. + + Call this function to save memory, if you are not interested in + the output of the process. + + \sa closeWriteChannel(), setReadChannel() +*/ +void QProcess::closeReadChannel(ProcessChannel channel) +{ + Q_D(QProcess); + + if (channel == StandardOutput) + d->stdoutChannel.closed = true; + else + d->stderrChannel.closed = true; +} + +/*! + Schedules the write channel of QProcess to be closed. The channel + will close once all data has been written to the process. After + calling this function, any attempts to write to the process will + fail. + + Closing the write channel is necessary for programs that read + input data until the channel has been closed. For example, the + program "more" is used to display text data in a console on both + Unix and Windows. But it will not display the text data until + QProcess's write channel has been closed. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 1 + + The write channel is implicitly opened when start() is called. + + \sa closeReadChannel() +*/ +void QProcess::closeWriteChannel() +{ + Q_D(QProcess); + d->stdinChannel.closed = true; // closing + if (d->writeBuffer.isEmpty()) + d->closeWriteChannel(); +} + +/*! + \since 4.2 + + Redirects the process' standard input to the file indicated by \a + fileName. When an input redirection is in place, the QProcess + object will be in read-only mode (calling write() will result in + error). + + If the file \a fileName does not exist at the moment start() is + called or is not readable, starting the process will fail. + + Calling setStandardInputFile() after the process has started has no + effect. + + \sa setStandardOutputFile(), setStandardErrorFile(), + setStandardOutputProcess() +*/ +void QProcess::setStandardInputFile(const QString &fileName) +{ + Q_D(QProcess); + d->stdinChannel = fileName; +} + +/*! + \since 4.2 + + Redirects the process' standard output to the file \a + fileName. When the redirection is in place, the standard output + read channel is closed: reading from it using read() will always + fail, as will readAllStandardOutput(). + + If the file \a fileName doesn't exist at the moment start() is + called, it will be created. If it cannot be created, the starting + will fail. + + If the file exists and \a mode is QIODevice::Truncate, the file + will be truncated. Otherwise (if \a mode is QIODevice::Append), + the file will be appended to. + + Calling setStandardOutputFile() after the process has started has + no effect. + + \sa setStandardInputFile(), setStandardErrorFile(), + setStandardOutputProcess() +*/ +void QProcess::setStandardOutputFile(const QString &fileName, OpenMode mode) +{ + Q_ASSERT(mode == Append || mode == Truncate); + Q_D(QProcess); + + d->stdoutChannel = fileName; + d->stdoutChannel.append = mode == Append; +} + +/*! + \since 4.2 + + Redirects the process' standard error to the file \a + fileName. When the redirection is in place, the standard error + read channel is closed: reading from it using read() will always + fail, as will readAllStandardError(). The file will be appended to + if \a mode is Append, otherwise, it will be truncated. + + See setStandardOutputFile() for more information on how the file + is opened. + + Note: if setProcessChannelMode() was called with an argument of + QProcess::MergedChannels, this function has no effect. + + \sa setStandardInputFile(), setStandardOutputFile(), + setStandardOutputProcess() +*/ +void QProcess::setStandardErrorFile(const QString &fileName, OpenMode mode) +{ + Q_ASSERT(mode == Append || mode == Truncate); + Q_D(QProcess); + + d->stderrChannel = fileName; + d->stderrChannel.append = mode == Append; +} + +/*! + \since 4.2 + + Pipes the standard output stream of this process to the \a + destination process' standard input. + + The following shell command: + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 2 + + Can be accomplished with QProcesses with the following code: + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 3 +*/ +void QProcess::setStandardOutputProcess(QProcess *destination) +{ + QProcessPrivate *dfrom = d_func(); + QProcessPrivate *dto = destination->d_func(); + dfrom->stdoutChannel.pipeTo(dto); + dto->stdinChannel.pipeFrom(dfrom); +} + +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + +/*! + \since 4.7 + + Returns the additional native command line arguments for the program. + + \note This function is available only on the Windows and Symbian + platforms. + + \sa setNativeArguments() +*/ +QString QProcess::nativeArguments() const +{ + Q_D(const QProcess); + return d->nativeArguments; +} + +/*! + \since 4.7 + \overload + + Sets additional native command line \a arguments for the program. + + On operating systems where the system API for passing command line + \a arguments to a subprocess natively uses a single string, one can + conceive command lines which cannot be passed via QProcess's portable + list-based API. In such cases this function must be used to set a + string which is \e appended to the string composed from the usual + argument list, with a delimiting space. + + \note This function is available only on the Windows and Symbian + platforms. + + \sa nativeArguments() +*/ +void QProcess::setNativeArguments(const QString &arguments) +{ + Q_D(QProcess); + d->nativeArguments = arguments; +} + +#endif + +/*! + If QProcess has been assigned a working directory, this function returns + the working directory that the QProcess will enter before the program has + started. Otherwise, (i.e., no directory has been assigned,) an empty + string is returned, and QProcess will use the application's current + working directory instead. + + \sa setWorkingDirectory() +*/ +QString QProcess::workingDirectory() const +{ + Q_D(const QProcess); + return d->workingDirectory; +} + +/*! + Sets the working directory to \a dir. QProcess will start the + process in this directory. The default behavior is to start the + process in the working directory of the calling process. + + \note The working directory setting is ignored on Symbian; + the private directory of the process is considered its working + directory. + + \sa workingDirectory(), start() +*/ +void QProcess::setWorkingDirectory(const QString &dir) +{ + Q_D(QProcess); + d->workingDirectory = dir; +} + +/*! + Returns the native process identifier for the running process, if + available. If no process is currently running, 0 is returned. +*/ +Q_PID QProcess::pid() const +{ + Q_D(const QProcess); + return d->pid; +} + +/*! \reimp + + This function operates on the current read channel. + + \sa readChannel(), setReadChannel() +*/ +bool QProcess::canReadLine() const +{ + Q_D(const QProcess); + const QRingBuffer *readBuffer = (d->processChannel == QProcess::StandardError) + ? &d->errorReadBuffer + : &d->outputReadBuffer; + return readBuffer->canReadLine() || QIODevice::canReadLine(); +} + +/*! + Closes all communication with the process and kills it. After calling this + function, QProcess will no longer emit readyRead(), and data can no + longer be read or written. +*/ +void QProcess::close() +{ + emit aboutToClose(); + while (waitForBytesWritten(-1)) + ; + kill(); + waitForFinished(-1); + QIODevice::close(); +} + +/*! \reimp + + Returns true if the process is not running, and no more data is available + for reading; otherwise returns false. +*/ +bool QProcess::atEnd() const +{ + Q_D(const QProcess); + const QRingBuffer *readBuffer = (d->processChannel == QProcess::StandardError) + ? &d->errorReadBuffer + : &d->outputReadBuffer; + return QIODevice::atEnd() && (!isOpen() || readBuffer->isEmpty()); +} + +/*! \reimp +*/ +bool QProcess::isSequential() const +{ + return true; +} + +/*! \reimp +*/ +qint64 QProcess::bytesAvailable() const +{ + Q_D(const QProcess); + const QRingBuffer *readBuffer = (d->processChannel == QProcess::StandardError) + ? &d->errorReadBuffer + : &d->outputReadBuffer; +#if defined QPROCESS_DEBUG + qDebug("QProcess::bytesAvailable() == %i (%s)", readBuffer->size(), + (d->processChannel == QProcess::StandardError) ? "stderr" : "stdout"); +#endif + return readBuffer->size() + QIODevice::bytesAvailable(); +} + +/*! \reimp +*/ +qint64 QProcess::bytesToWrite() const +{ + Q_D(const QProcess); + qint64 size = d->writeBuffer.size(); +#ifdef Q_OS_WIN + size += d->pipeWriterBytesToWrite(); +#endif + return size; +} + +/*! + Returns the type of error that occurred last. + + \sa state() +*/ +QProcess::ProcessError QProcess::error() const +{ + Q_D(const QProcess); + return d->processError; +} + +/*! + Returns the current state of the process. + + \sa stateChanged(), error() +*/ +QProcess::ProcessState QProcess::state() const +{ + Q_D(const QProcess); + return d->processState; +} + +/*! + \deprecated + Sets the environment that QProcess will use when starting a process to the + \a environment specified which consists of a list of key=value pairs. + + For example, the following code adds the \c{C:\\BIN} directory to the list of + executable paths (\c{PATHS}) on Windows: + + \snippet doc/src/snippets/qprocess-environment/main.cpp 0 + + \note This function is less efficient than the setProcessEnvironment() + function. + + \sa environment(), setProcessEnvironment(), systemEnvironment() +*/ +void QProcess::setEnvironment(const QStringList &environment) +{ + setProcessEnvironment(QProcessEnvironmentPrivate::fromList(environment)); +} + +/*! + \deprecated + Returns the environment that QProcess will use when starting a + process, or an empty QStringList if no environment has been set + using setEnvironment() or setEnvironmentHash(). If no environment + has been set, the environment of the calling process will be used. + + \note The environment settings are ignored on Windows CE and Symbian, + as there is no concept of an environment. + + \sa processEnvironment(), setEnvironment(), systemEnvironment() +*/ +QStringList QProcess::environment() const +{ + Q_D(const QProcess); + return d->environment.toStringList(); +} + +/*! + \since 4.6 + Sets the environment that QProcess will use when starting a process to the + \a environment object. + + For example, the following code adds the \c{C:\\BIN} directory to the list of + executable paths (\c{PATHS}) on Windows and sets \c{TMPDIR}: + + \snippet doc/src/snippets/qprocess-environment/main.cpp 1 + + Note how, on Windows, environment variable names are case-insensitive. + + \sa processEnvironment(), QProcessEnvironment::systemEnvironment(), setEnvironment() +*/ +void QProcess::setProcessEnvironment(const QProcessEnvironment &environment) +{ + Q_D(QProcess); + d->environment = environment; +} + +/*! + \since 4.6 + Returns the environment that QProcess will use when starting a + process, or an empty object if no environment has been set using + setEnvironment() or setProcessEnvironment(). If no environment has + been set, the environment of the calling process will be used. + + \note The environment settings are ignored on Windows CE, + as there is no concept of an environment. + + \sa setProcessEnvironment(), setEnvironment(), QProcessEnvironment::isEmpty() +*/ +QProcessEnvironment QProcess::processEnvironment() const +{ + Q_D(const QProcess); + return d->environment; +} + +/*! + Blocks until the process has started and the started() signal has + been emitted, or until \a msecs milliseconds have passed. + + Returns true if the process was started successfully; otherwise + returns false (if the operation timed out or if an error + occurred). + + This function can operate without an event loop. It is + useful when writing non-GUI applications and when performing + I/O operations in a non-GUI thread. + + \warning Calling this function from the main (GUI) thread + might cause your user interface to freeze. + + If msecs is -1, this function will not time out. + + \sa started(), waitForReadyRead(), waitForBytesWritten(), waitForFinished() +*/ +bool QProcess::waitForStarted(int msecs) +{ + Q_D(QProcess); + if (d->processState == QProcess::Starting) { + if (!d->waitForStarted(msecs)) + return false; + setProcessState(QProcess::Running); + emit started(); + } + return d->processState == QProcess::Running; +} + +/*! \reimp +*/ +bool QProcess::waitForReadyRead(int msecs) +{ + Q_D(QProcess); + + if (d->processState == QProcess::NotRunning) + return false; + if (d->processChannel == QProcess::StandardOutput && d->stdoutChannel.closed) + return false; + if (d->processChannel == QProcess::StandardError && d->stderrChannel.closed) + return false; + return d->waitForReadyRead(msecs); +} + +/*! \reimp +*/ +bool QProcess::waitForBytesWritten(int msecs) +{ + Q_D(QProcess); + if (d->processState == QProcess::NotRunning) + return false; + if (d->processState == QProcess::Starting) { + QElapsedTimer stopWatch; + stopWatch.start(); + bool started = waitForStarted(msecs); + if (!started) + return false; + if (msecs != -1) + msecs -= stopWatch.elapsed(); + } + + return d->waitForBytesWritten(msecs); +} + +/*! + Blocks until the process has finished and the finished() signal + has been emitted, or until \a msecs milliseconds have passed. + + Returns true if the process finished; otherwise returns false (if + the operation timed out, if an error occurred, or if this QProcess + is already finished). + + This function can operate without an event loop. It is + useful when writing non-GUI applications and when performing + I/O operations in a non-GUI thread. + + \warning Calling this function from the main (GUI) thread + might cause your user interface to freeze. + + If msecs is -1, this function will not time out. + + \sa finished(), waitForStarted(), waitForReadyRead(), waitForBytesWritten() +*/ +bool QProcess::waitForFinished(int msecs) +{ + Q_D(QProcess); + if (d->processState == QProcess::NotRunning) + return false; + if (d->processState == QProcess::Starting) { + QElapsedTimer stopWatch; + stopWatch.start(); + bool started = waitForStarted(msecs); + if (!started) + return false; + if (msecs != -1) + msecs -= stopWatch.elapsed(); + } + + return d->waitForFinished(msecs); +} + +/*! + Sets the current state of the QProcess to the \a state specified. + + \sa state() +*/ +void QProcess::setProcessState(ProcessState state) +{ + Q_D(QProcess); + if (d->processState == state) + return; + d->processState = state; + emit stateChanged(state); +} + +/*! + This function is called in the child process context just before the + program is executed on Unix or Mac OS X (i.e., after \e fork(), but before + \e execve()). Reimplement this function to do last minute initialization + of the child process. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 4 + + You cannot exit the process (by calling exit(), for instance) from + this function. If you need to stop the program before it starts + execution, your workaround is to emit finished() and then call + exit(). + + \warning This function is called by QProcess on Unix and Mac OS X + only. On Windows, it is not called. +*/ +void QProcess::setupChildProcess() +{ +} + +/*! \reimp +*/ +qint64 QProcess::readData(char *data, qint64 maxlen) +{ + Q_D(QProcess); + QRingBuffer *readBuffer = (d->processChannel == QProcess::StandardError) + ? &d->errorReadBuffer + : &d->outputReadBuffer; + + if (maxlen == 1 && !readBuffer->isEmpty()) { + int c = readBuffer->getChar(); + if (c == -1) { +#if defined QPROCESS_DEBUG + qDebug("QProcess::readData(%p \"%s\", %d) == -1", + data, qt_prettyDebug(data, 1, maxlen).constData(), 1); +#endif + return -1; + } + *data = (char) c; +#if defined QPROCESS_DEBUG + qDebug("QProcess::readData(%p \"%s\", %d) == 1", + data, qt_prettyDebug(data, 1, maxlen).constData(), 1); +#endif + return 1; + } + + qint64 bytesToRead = qint64(qMin(readBuffer->size(), (int)maxlen)); + qint64 readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readBuffer->readPointer(); + int bytesToReadFromThisBlock = qMin<qint64>(bytesToRead - readSoFar, + readBuffer->nextDataBlockSize()); + memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + readBuffer->free(bytesToReadFromThisBlock); + } + +#if defined QPROCESS_DEBUG + qDebug("QProcess::readData(%p \"%s\", %lld) == %lld", + data, qt_prettyDebug(data, readSoFar, 16).constData(), maxlen, readSoFar); +#endif + if (!readSoFar && d->processState == QProcess::NotRunning) + return -1; // EOF + return readSoFar; +} + +/*! \reimp +*/ +qint64 QProcess::writeData(const char *data, qint64 len) +{ + Q_D(QProcess); + +#if defined(Q_OS_WINCE) + Q_UNUSED(data); + Q_UNUSED(len); + d->processError = QProcess::WriteError; + setErrorString(tr("Error writing to process")); + emit error(d->processError); + return -1; +#endif + + if (d->stdinChannel.closed) { +#if defined QPROCESS_DEBUG + qDebug("QProcess::writeData(%p \"%s\", %lld) == 0 (write channel closing)", + data, qt_prettyDebug(data, len, 16).constData(), len); +#endif + return 0; + } + + if (len == 1) { + d->writeBuffer.putChar(*data); + if (d->stdinChannel.notifier) + d->stdinChannel.notifier->setEnabled(true); +#if defined QPROCESS_DEBUG + qDebug("QProcess::writeData(%p \"%s\", %lld) == 1 (written to buffer)", + data, qt_prettyDebug(data, len, 16).constData(), len); +#endif + return 1; + } + + char *dest = d->writeBuffer.reserve(len); + memcpy(dest, data, len); + if (d->stdinChannel.notifier) + d->stdinChannel.notifier->setEnabled(true); +#if defined QPROCESS_DEBUG + qDebug("QProcess::writeData(%p \"%s\", %lld) == %lld (written to buffer)", + data, qt_prettyDebug(data, len, 16).constData(), len, len); +#endif + return len; +} + +/*! + Regardless of the current read channel, this function returns all + data available from the standard output of the process as a + QByteArray. + + \sa readyReadStandardOutput(), readAllStandardError(), readChannel(), setReadChannel() +*/ +QByteArray QProcess::readAllStandardOutput() +{ + ProcessChannel tmp = readChannel(); + setReadChannel(StandardOutput); + QByteArray data = readAll(); + setReadChannel(tmp); + return data; +} + +/*! + Regardless of the current read channel, this function returns all + data available from the standard error of the process as a + QByteArray. + + \sa readyReadStandardError(), readAllStandardOutput(), readChannel(), setReadChannel() +*/ +QByteArray QProcess::readAllStandardError() +{ + ProcessChannel tmp = readChannel(); + setReadChannel(StandardError); + QByteArray data = readAll(); + setReadChannel(tmp); + return data; +} + +/*! + Starts the given \a program in a new process, if none is already + running, passing the command line arguments in \a arguments. The OpenMode + is set to \a mode. + + The QProcess object will immediately enter the Starting state. If the + process starts successfully, QProcess will emit started(); otherwise, + error() will be emitted. If the QProcess object is already running a + process, a warning may be printed at the console, and the existing + process will continue running. + + \note Processes are started asynchronously, which means the started() + and error() signals may be delayed. Call waitForStarted() to make + sure the process has started (or has failed to start) and those signals + have been emitted. + + \note No further splitting of the arguments is performed. + + \bold{Windows:} Arguments that contain spaces are wrapped in quotes. + + \sa pid(), started(), waitForStarted() +*/ +void QProcess::start(const QString &program, const QStringList &arguments, OpenMode mode) +{ + Q_D(QProcess); + if (d->processState != NotRunning) { + qWarning("QProcess::start: Process is already running"); + return; + } + +#if defined QPROCESS_DEBUG + qDebug() << "QProcess::start(" << program << ',' << arguments << ',' << mode << ')'; +#endif + + d->outputReadBuffer.clear(); + d->errorReadBuffer.clear(); + + if (d->stdinChannel.type != QProcessPrivate::Channel::Normal) + mode &= ~WriteOnly; // not open for writing + if (d->stdoutChannel.type != QProcessPrivate::Channel::Normal && + (d->stderrChannel.type != QProcessPrivate::Channel::Normal || + d->processChannelMode == MergedChannels)) + mode &= ~ReadOnly; // not open for reading + if (mode == 0) + mode = Unbuffered; + QIODevice::open(mode); + + d->stdinChannel.closed = false; + d->stdoutChannel.closed = false; + d->stderrChannel.closed = false; + + d->program = program; + d->arguments = arguments; + + d->exitCode = 0; + d->exitStatus = NormalExit; + d->processError = QProcess::UnknownError; + d->errorString.clear(); + d->startProcess(); +} + + +static QStringList parseCombinedArgString(const QString &program) +{ + QStringList args; + QString tmp; + int quoteCount = 0; + bool inQuote = false; + + // handle quoting. tokens can be surrounded by double quotes + // "hello world". three consecutive double quotes represent + // the quote character itself. + for (int i = 0; i < program.size(); ++i) { + if (program.at(i) == QLatin1Char('"')) { + ++quoteCount; + if (quoteCount == 3) { + // third consecutive quote + quoteCount = 0; + tmp += program.at(i); + } + continue; + } + if (quoteCount) { + if (quoteCount == 1) + inQuote = !inQuote; + quoteCount = 0; + } + if (!inQuote && program.at(i).isSpace()) { + if (!tmp.isEmpty()) { + args += tmp; + tmp.clear(); + } + } else { + tmp += program.at(i); + } + } + if (!tmp.isEmpty()) + args += tmp; + + return args; +} + +/*! + \overload + + Starts the program \a program in a new process, if one is not already + running. \a program is a single string of text containing both the + program name and its arguments. The arguments are separated by one or + more spaces. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 5 + + The \a program string can also contain quotes, to ensure that arguments + containing spaces are correctly supplied to the new process. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 6 + + If the QProcess object is already running a process, a warning may be + printed at the console, and the existing process will continue running. + + Note that, on Windows, quotes need to be both escaped and quoted. + For example, the above code would be specified in the following + way to ensure that \c{"My Documents"} is used as the argument to + the \c dir executable: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 7 + + The OpenMode is set to \a mode. +*/ +void QProcess::start(const QString &program, OpenMode mode) +{ + QStringList args = parseCombinedArgString(program); + if (args.isEmpty()) { + Q_D(QProcess); + d->processError = QProcess::FailedToStart; + setErrorString(tr("No program defined")); + emit error(d->processError); + return; + } + + QString prog = args.first(); + args.removeFirst(); + + start(prog, args, mode); +} + +/*! + Attempts to terminate the process. + + The process may not exit as a result of calling this function (it is given + the chance to prompt the user for any unsaved files, etc). + + On Windows, terminate() posts a WM_CLOSE message to all toplevel windows + of the process and then to the main thread of the process itself. On Unix + and Mac OS X the SIGTERM signal is sent. + + Console applications on Windows that do not run an event loop, or whose + event loop does not handle the WM_CLOSE message, can only be terminated by + calling kill(). + + On Symbian, this function requires platform security capability + \c PowerMgmt. If absent, the process will panic with KERN-EXEC 46. + + \note Terminating running processes from other processes will typically + cause a panic in Symbian due to platform security. + + \sa {Symbian Platform Security Requirements} + \sa kill() +*/ +void QProcess::terminate() +{ + Q_D(QProcess); + d->terminateProcess(); +} + +/*! + Kills the current process, causing it to exit immediately. + + On Windows, kill() uses TerminateProcess, and on Unix and Mac OS X, the + SIGKILL signal is sent to the process. + + On Symbian, this function requires platform security capability + \c PowerMgmt. If absent, the process will panic with KERN-EXEC 46. + + \note Killing running processes from other processes will typically + cause a panic in Symbian due to platform security. + + \sa {Symbian Platform Security Requirements} + \sa terminate() +*/ +void QProcess::kill() +{ + Q_D(QProcess); + d->killProcess(); +} + +/*! + Returns the exit code of the last process that finished. +*/ +int QProcess::exitCode() const +{ + Q_D(const QProcess); + return d->exitCode; +} + +/*! + \since 4.1 + + Returns the exit status of the last process that finished. + + On Windows, if the process was terminated with TerminateProcess() + from another application this function will still return NormalExit + unless the exit code is less than 0. +*/ +QProcess::ExitStatus QProcess::exitStatus() const +{ + Q_D(const QProcess); + return d->exitStatus; +} + +/*! + Starts the program \a program with the arguments \a arguments in a + new process, waits for it to finish, and then returns the exit + code of the process. Any data the new process writes to the + console is forwarded to the calling process. + + The environment and working directory are inherited from the calling + process. + + On Windows, arguments that contain spaces are wrapped in quotes. + + If the process cannot be started, -2 is returned. If the process + crashes, -1 is returned. Otherwise, the process' exit code is + returned. +*/ +int QProcess::execute(const QString &program, const QStringList &arguments) +{ + QProcess process; + process.setReadChannelMode(ForwardedChannels); + process.start(program, arguments); + if (!process.waitForFinished(-1)) + return -2; + return process.exitStatus() == QProcess::NormalExit ? process.exitCode() : -1; +} + +/*! + \overload + + Starts the program \a program in a new process. \a program is a + single string of text containing both the program name and its + arguments. The arguments are separated by one or more spaces. +*/ +int QProcess::execute(const QString &program) +{ + QProcess process; + process.setReadChannelMode(ForwardedChannels); + process.start(program); + if (!process.waitForFinished(-1)) + return -2; + return process.exitStatus() == QProcess::NormalExit ? process.exitCode() : -1; +} + +/*! + Starts the program \a program with the arguments \a arguments in a + new process, and detaches from it. Returns true on success; + otherwise returns false. If the calling process exits, the + detached process will continue to live. + + Note that arguments that contain spaces are not passed to the + process as separate arguments. + + \bold{Unix:} The started process will run in its own session and act + like a daemon. + + \bold{Windows:} Arguments that contain spaces are wrapped in quotes. + The started process will run as a regular standalone process. + + The process will be started in the directory \a workingDirectory. + + If the function is successful then *\a pid is set to the process + identifier of the started process. +*/ +bool QProcess::startDetached(const QString &program, + const QStringList &arguments, + const QString &workingDirectory, + qint64 *pid) +{ + return QProcessPrivate::startDetached(program, + arguments, + workingDirectory, + pid); +} + +/*! + Starts the program \a program with the given \a arguments in a + new process, and detaches from it. Returns true on success; + otherwise returns false. If the calling process exits, the + detached process will continue to live. + + \note Arguments that contain spaces are not passed to the + process as separate arguments. + + \bold{Unix:} The started process will run in its own session and act + like a daemon. + + \bold{Windows:} Arguments that contain spaces are wrapped in quotes. + The started process will run as a regular standalone process. +*/ +bool QProcess::startDetached(const QString &program, + const QStringList &arguments) +{ + return QProcessPrivate::startDetached(program, arguments); +} + +/*! + \overload + + Starts the program \a program in a new process. \a program is a + single string of text containing both the program name and its + arguments. The arguments are separated by one or more spaces. + + The \a program string can also contain quotes, to ensure that arguments + containing spaces are correctly supplied to the new process. +*/ +bool QProcess::startDetached(const QString &program) +{ + QStringList args = parseCombinedArgString(program); + if (args.isEmpty()) + return false; + + QString prog = args.first(); + args.removeFirst(); + + return QProcessPrivate::startDetached(prog, args); +} + +QT_BEGIN_INCLUDE_NAMESPACE +#if defined(Q_OS_MAC) && !defined(QT_NO_CORESERVICES) +# include <crt_externs.h> +# define environ (*_NSGetEnviron()) +#elif defined(Q_OS_WINCE) || defined(Q_OS_SYMBIAN) || (defined(Q_OS_MAC) && defined(QT_NO_CORESERVICES)) + static char *qt_empty_environ[] = { 0 }; +#define environ qt_empty_environ +#elif !defined(Q_OS_WIN) + extern char **environ; +#endif +QT_END_INCLUDE_NAMESPACE + +/*! + \since 4.1 + + Returns the environment of the calling process as a list of + key=value pairs. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 8 + + This function does not cache the system environment. Therefore, it's + possible to obtain an updated version of the environment if low-level C + library functions like \tt setenv ot \tt putenv have been called. + + However, note that repeated calls to this function will recreate the + list of environment variables, which is a non-trivial operation. + + \note For new code, it is recommended to use QProcessEvironment::systemEnvironment() + + \sa QProcessEnvironment::systemEnvironment(), environment(), setEnvironment() +*/ +QStringList QProcess::systemEnvironment() +{ + QStringList tmp; + char *entry = 0; + int count = 0; + while ((entry = environ[count++])) + tmp << QString::fromLocal8Bit(entry); + return tmp; +} + +/*! + \since 4.6 + + \brief The systemEnvironment function returns the environment of + the calling process. + + It is returned as a QProcessEnvironment. This function does not + cache the system environment. Therefore, it's possible to obtain + an updated version of the environment if low-level C library + functions like \tt setenv ot \tt putenv have been called. + + However, note that repeated calls to this function will recreate the + QProcessEnvironment object, which is a non-trivial operation. + + \sa QProcess::systemEnvironment() +*/ +QProcessEnvironment QProcessEnvironment::systemEnvironment() +{ + QProcessEnvironment env; + const char *entry; + for (int count = 0; (entry = environ[count]); ++count) { + const char *equal = strchr(entry, '='); + if (!equal) + continue; + + QByteArray name(entry, equal - entry); + QByteArray value(equal + 1); + env.insert(QString::fromLocal8Bit(name), QString::fromLocal8Bit(value)); + } + return env; +} + +/*! + \typedef Q_PID + \relates QProcess + + Typedef for the identifiers used to represent processes on the underlying + platform. On Unix and Symbian, this corresponds to \l qint64; on Windows, it + corresponds to \c{_PROCESS_INFORMATION*}. + + \sa QProcess::pid() +*/ + +QT_END_NAMESPACE + +#include "moc_qprocess.cpp" + +#endif // QT_NO_PROCESS + diff --git a/src/corelib/io/qprocess.h b/src/corelib/io/qprocess.h new file mode 100644 index 0000000000..664992f7e2 --- /dev/null +++ b/src/corelib/io/qprocess.h @@ -0,0 +1,245 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROCESS_H +#define QPROCESS_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +#ifndef QT_NO_PROCESS + +#if (!defined(Q_OS_WIN32) && !defined(Q_OS_WINCE)) || defined(qdoc) +typedef qint64 Q_PID; +#else +QT_END_NAMESPACE +typedef struct _PROCESS_INFORMATION *Q_PID; +QT_BEGIN_NAMESPACE +#endif + +class QProcessPrivate; +class QProcessEnvironmentPrivate; + +class Q_CORE_EXPORT QProcessEnvironment +{ +public: + QProcessEnvironment(); + QProcessEnvironment(const QProcessEnvironment &other); + ~QProcessEnvironment(); + QProcessEnvironment &operator=(const QProcessEnvironment &other); + + bool operator==(const QProcessEnvironment &other) const; + inline bool operator!=(const QProcessEnvironment &other) const + { return !(*this == other); } + + bool isEmpty() const; + void clear(); + + bool contains(const QString &name) const; + void insert(const QString &name, const QString &value); + void remove(const QString &name); + QString value(const QString &name, const QString &defaultValue = QString()) const; + + QStringList toStringList() const; + + QStringList keys() const; + + void insert(const QProcessEnvironment &e); + + static QProcessEnvironment systemEnvironment(); + +private: + friend class QProcessPrivate; + friend class QProcessEnvironmentPrivate; + QSharedDataPointer<QProcessEnvironmentPrivate> d; +}; + +class Q_CORE_EXPORT QProcess : public QIODevice +{ + Q_OBJECT +public: + enum ProcessError { + FailedToStart, //### file not found, resource error + Crashed, + Timedout, + ReadError, + WriteError, + UnknownError + }; + enum ProcessState { + NotRunning, + Starting, + Running + }; + enum ProcessChannel { + StandardOutput, + StandardError + }; + enum ProcessChannelMode { + SeparateChannels, + MergedChannels, + ForwardedChannels + }; + enum ExitStatus { + NormalExit, + CrashExit + }; + + explicit QProcess(QObject *parent = 0); + virtual ~QProcess(); + + void start(const QString &program, const QStringList &arguments, OpenMode mode = ReadWrite); + void start(const QString &program, OpenMode mode = ReadWrite); + + ProcessChannelMode readChannelMode() const; + void setReadChannelMode(ProcessChannelMode mode); + ProcessChannelMode processChannelMode() const; + void setProcessChannelMode(ProcessChannelMode mode); + + ProcessChannel readChannel() const; + void setReadChannel(ProcessChannel channel); + + void closeReadChannel(ProcessChannel channel); + void closeWriteChannel(); + + void setStandardInputFile(const QString &fileName); + void setStandardOutputFile(const QString &fileName, OpenMode mode = Truncate); + void setStandardErrorFile(const QString &fileName, OpenMode mode = Truncate); + void setStandardOutputProcess(QProcess *destination); + +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + QString nativeArguments() const; + void setNativeArguments(const QString &arguments); +#endif + + QString workingDirectory() const; + void setWorkingDirectory(const QString &dir); + + void setEnvironment(const QStringList &environment); + QStringList environment() const; + void setProcessEnvironment(const QProcessEnvironment &environment); + QProcessEnvironment processEnvironment() const; + + QProcess::ProcessError error() const; + QProcess::ProcessState state() const; + + // #### Qt 5: Q_PID is a pointer on Windows and a value on Unix + Q_PID pid() const; + + bool waitForStarted(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + bool waitForBytesWritten(int msecs = 30000); + bool waitForFinished(int msecs = 30000); + + QByteArray readAllStandardOutput(); + QByteArray readAllStandardError(); + + int exitCode() const; + QProcess::ExitStatus exitStatus() const; + + // QIODevice + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + bool isSequential() const; + bool canReadLine() const; + void close(); + bool atEnd() const; + + static int execute(const QString &program, const QStringList &arguments); + static int execute(const QString &program); + + static bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, + qint64 *pid = 0); + static bool startDetached(const QString &program, const QStringList &arguments); + static bool startDetached(const QString &program); + + static QStringList systemEnvironment(); + +public Q_SLOTS: + void terminate(); + void kill(); + +Q_SIGNALS: + void started(); + void finished(int exitCode); + void finished(int exitCode, QProcess::ExitStatus exitStatus); + void error(QProcess::ProcessError error); + void stateChanged(QProcess::ProcessState state); + + void readyReadStandardOutput(); + void readyReadStandardError(); + +protected: + void setProcessState(ProcessState state); + + virtual void setupChildProcess(); + + // QIODevice + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + +private: + Q_DECLARE_PRIVATE(QProcess) + Q_DISABLE_COPY(QProcess) + + Q_PRIVATE_SLOT(d_func(), bool _q_canReadStandardOutput()) + Q_PRIVATE_SLOT(d_func(), bool _q_canReadStandardError()) + Q_PRIVATE_SLOT(d_func(), bool _q_canWrite()) + Q_PRIVATE_SLOT(d_func(), bool _q_startupNotification()) + Q_PRIVATE_SLOT(d_func(), bool _q_processDied()) + Q_PRIVATE_SLOT(d_func(), void _q_notified()) + friend class QProcessManager; +}; + +#endif // QT_NO_PROCESS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPROCESS_H diff --git a/src/corelib/io/qprocess_p.h b/src/corelib/io/qprocess_p.h new file mode 100644 index 0000000000..7bfcb311f9 --- /dev/null +++ b/src/corelib/io/qprocess_p.h @@ -0,0 +1,260 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROCESS_P_H +#define QPROCESS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qprocess.h" +#include "QtCore/qstringlist.h" +#include "QtCore/qhash.h" +#include "QtCore/qshareddata.h" +#include "private/qringbuffer_p.h" +#include "private/qiodevice_p.h" + +#ifdef Q_OS_WIN +#include "QtCore/qt_windows.h" +typedef HANDLE Q_PIPE; +#define INVALID_Q_PIPE INVALID_HANDLE_VALUE +#else +typedef int Q_PIPE; +#define INVALID_Q_PIPE -1 +#endif + +#ifndef QT_NO_PROCESS + +QT_BEGIN_NAMESPACE + +class QSocketNotifier; +class QWindowsPipeWriter; +class QWinEventNotifier; +class QTimer; +#if defined(Q_OS_SYMBIAN) +class RProcess; +#endif + +class QProcessEnvironmentPrivate: public QSharedData +{ +public: +#ifdef Q_OS_WIN + typedef QString Unit; +#else + typedef QByteArray Unit; +#endif + typedef QHash<Unit, Unit> Hash; + Hash hash; + + static QProcessEnvironment fromList(const QStringList &list); + QStringList toList() const; + QStringList keys() const; + void insert(const Hash &hash); +}; + +class QProcessPrivate : public QIODevicePrivate +{ +public: + Q_DECLARE_PUBLIC(QProcess) + + struct Channel { + enum ProcessChannelType { + Normal = 0, + PipeSource = 1, + PipeSink = 2, + Redirect = 3 + // if you add "= 4" here, increase the number of bits below + }; + + Channel() : process(0), notifier(0), type(Normal), closed(false), append(false) + { + pipe[0] = INVALID_Q_PIPE; + pipe[1] = INVALID_Q_PIPE; + } + + void clear(); + + Channel &operator=(const QString &fileName) + { + clear(); + file = fileName; + type = fileName.isEmpty() ? Normal : Redirect; + return *this; + } + + void pipeTo(QProcessPrivate *other) + { + clear(); + process = other; + type = PipeSource; + } + + void pipeFrom(QProcessPrivate *other) + { + clear(); + process = other; + type = PipeSink; + } + + QString file; + QProcessPrivate *process; + QSocketNotifier *notifier; + Q_PIPE pipe[2]; + + unsigned type : 2; + bool closed : 1; + bool append : 1; + }; + + QProcessPrivate(); + virtual ~QProcessPrivate(); + + // private slots + bool _q_canReadStandardOutput(); + bool _q_canReadStandardError(); + bool _q_canWrite(); + bool _q_startupNotification(); + bool _q_processDied(); + void _q_notified(); + + QProcess::ProcessChannel processChannel; + QProcess::ProcessChannelMode processChannelMode; + QProcess::ProcessError processError; + QProcess::ProcessState processState; + QString workingDirectory; + Q_PID pid; + int sequenceNumber; + + bool dying; + bool emittedReadyRead; + bool emittedBytesWritten; + + Channel stdinChannel; + Channel stdoutChannel; + Channel stderrChannel; + bool createChannel(Channel &channel); + void closeWriteChannel(); + + QString program; + QStringList arguments; +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + QString nativeArguments; +#endif + QProcessEnvironment environment; + + QRingBuffer outputReadBuffer; + QRingBuffer errorReadBuffer; + QRingBuffer writeBuffer; + + Q_PIPE childStartedPipe[2]; + Q_PIPE deathPipe[2]; + void destroyPipe(Q_PIPE pipe[2]); + + QSocketNotifier *startupSocketNotifier; + QSocketNotifier *deathNotifier; + + // the wonderful windows notifier + QTimer *notifier; + QWindowsPipeWriter *pipeWriter; + QWinEventNotifier *processFinishedNotifier; + + void startProcess(); +#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN) + void execChild(const char *workingDirectory, char **path, char **argv, char **envp); +#endif + bool processStarted(); + void terminateProcess(); + void killProcess(); + void findExitCode(); +#ifdef Q_OS_UNIX + bool waitForDeadChild(); +#endif +#ifdef Q_OS_WIN + void flushPipeWriter(); + qint64 pipeWriterBytesToWrite() const; +#endif + + static bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory = QString(), + qint64 *pid = 0); + + int exitCode; + QProcess::ExitStatus exitStatus; + bool crashed; +#ifdef Q_OS_UNIX + int serial; +#endif + + bool waitForStarted(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + bool waitForBytesWritten(int msecs = 30000); + bool waitForFinished(int msecs = 30000); + bool waitForWrite(int msecs = 30000); + + qint64 bytesAvailableFromStdout() const; + qint64 bytesAvailableFromStderr() const; + qint64 readFromStdout(char *data, qint64 maxlen); + qint64 readFromStderr(char *data, qint64 maxlen); + qint64 writeToStdin(const char *data, qint64 maxlen); + + void cleanup(); +#ifdef Q_OS_UNIX + static void initializeProcessManager(); +#endif + +#ifdef Q_OS_SYMBIAN + bool processLaunched; + RProcess* symbianProcess; +#endif +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PROCESS + +#endif // QPROCESS_P_H diff --git a/src/corelib/io/qprocess_symbian.cpp b/src/corelib/io/qprocess_symbian.cpp new file mode 100644 index 0000000000..8a74c7b4a2 --- /dev/null +++ b/src/corelib/io/qprocess_symbian.cpp @@ -0,0 +1,1067 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QPROCESS_DEBUG + +#ifdef QPROCESS_DEBUG +#include "qdebug.h" +#define QPROCESS_DEBUG_PRINT(args...) qDebug(args); +#else +#define QPROCESS_DEBUG_PRINT(args...) +#endif + +#ifndef QT_NO_PROCESS + +#define QPROCESS_ASSERT(check, panicReason, args...) \ + if (!(check)) { \ + qWarning(args); \ + User::Panic(KQProcessPanic, panicReason); \ + } + +#include <exception> +#include <e32base.h> +#include <e32std.h> +#include <stdio.h> +#include "qplatformdefs.h" + +#include "qdir.h" +#include "qstring.h" +#include "qprocess.h" +#include "qprocess_p.h" +#include "private/qeventdispatcher_symbian_p.h" + +#include <private/qthread_p.h> +#include <qmutex.h> +#include <qmap.h> +#include <qsocketnotifier.h> + +#include <errno.h> + + +QT_BEGIN_NAMESPACE + +_LIT(KQProcessManagerThreadName, "QProcManThread"); +_LIT(KQProcessPanic, "QPROCESS"); +enum TQProcessPanic { + EProcessManagerMediatorRunError = 1, + EProcessManagerMediatorInactive = 2, + EProcessManagerMediatorNotPending = 3, + EProcessManagerMediatorInvalidCmd = 4, + EProcessManagerMediatorCreationFailed = 5, + EProcessManagerMediatorThreadOpenFailed = 6, + EProcessManagerMediatorNullObserver = 7, + EProcessActiveRunError = 10, + EProcessActiveNullParameter = 11, + EProcessManagerMutexCreationFail = 20, + EProcessManagerThreadCreationFail = 21, + EProcessManagerSchedulerCreationFail = 22, + EProcessManagerNullParam = 23 +}; + +// Forward declarations +class QProcessManager; + + +// Active object to listen for child process death +class QProcessActive : public CActive +{ +public: + static QProcessActive *construct(QProcess *process, + RProcess **proc, + int serial, + int deathPipe); + + virtual ~QProcessActive(); + + void start(); + void stop(); + + bool error(); + +protected: + + // Inherited from CActive + void RunL(); + TInt RunError(TInt aError); + void DoCancel(); + + QProcessActive(); + +private: + + QProcess *process; + RProcess **pproc; + int serial; + int deathPipe; + bool errorValue; +}; + +// Active object to communicate synchronously with process manager thread +class QProcessManagerMediator : public CActive +{ +public: + static QProcessManagerMediator *construct(); + + virtual ~QProcessManagerMediator(); + + bool add(QProcessActive *processObserver); + void remove(QProcessActive *processObserver); + void terminate(); + +protected: + + enum Commands { + ENoCommand, + EAdd, + ERemove, + ETerminate + }; + + // Inherited from CActive + void RunL(); + TInt RunError(TInt aError); + void DoCancel(); + + QProcessManagerMediator(); + + bool notify(QProcessActive *processObserver, Commands command); + +private: + QProcessActive *currentObserver; + Commands currentCommand; + + RThread processManagerThread; +}; + +// Process manager manages child process death listeners. +// +// Note: Because QProcess can be used outside event loop, we cannot be guaranteed +// an active scheduler exists for us to add our process death listener objects. +// We can't just install active scheduler on the calling thread, as that would block it +// if we want to actually use it, so a separate manager thread is required. +class QProcessManager +{ +public: + QProcessManager(); + ~QProcessManager(); + + void startThread(); + + TInt run(void *param); + bool add(QProcess *process); + void remove(QProcess *process); + + inline void setMediator(QProcessManagerMediator *newMediator) { + mediator = newMediator; + }; + +private: + inline void lock() { + managerMutex.Wait(); + }; + inline void unlock() { + managerMutex.Signal(); + }; + + QMap<int, QProcessActive *> children; + QProcessManagerMediator *mediator; + RMutex managerMutex; + bool threadStarted; + RThread managerThread; +}; + +static bool qt_rprocess_running(RProcess *proc) +{ + if (proc && proc->Handle()) { + TExitType et = proc->ExitType(); + if (et == EExitPending) + return true; + } + + return false; +} + +static void qt_create_symbian_commandline( + const QStringList &arguments, const QString &nativeArguments, QString &commandLine) +{ + for (int i = 0; i < arguments.size(); ++i) { + QString tmp = arguments.at(i); + // in the case of \" already being in the string the \ must also be escaped + tmp.replace(QLatin1String("\\\""), QLatin1String("\\\\\"")); + // escape a single " because the arguments will be parsed + tmp.replace(QLatin1String("\""), QLatin1String("\\\"")); + if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) { + // The argument must not end with a \ since this would be interpreted + // as escaping the quote -- rather put the \ behind the quote: e.g. + // rather use "foo"\ than "foo\" + QString endQuote(QLatin1String("\"")); + int i = tmp.length(); + while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\')) { + --i; + endQuote += QLatin1String("\\"); + } + commandLine += QLatin1String("\"") + tmp.left(i) + endQuote + QLatin1Char(' '); + } else { + commandLine += tmp + QLatin1Char(' '); + } + } + + if (!nativeArguments.isEmpty()) + commandLine += nativeArguments; + else if (!commandLine.isEmpty()) // Chop the extra trailing space if any arguments were appended + commandLine.chop(1); +} + +static TInt qt_create_symbian_process(RProcess **proc, + const QString &programName, const QStringList &arguments, const QString &nativeArguments) +{ + RProcess *newProc = NULL; + newProc = new RProcess(); + + if (!newProc) + return KErrNoMemory; + + QString commandLine; + qt_create_symbian_commandline(arguments, nativeArguments, commandLine); + + TPtrC program_ptr(reinterpret_cast<const TText*>(programName.constData())); + TPtrC cmdline_ptr(reinterpret_cast<const TText*>(commandLine.constData())); + + TInt err = newProc->Create(program_ptr, cmdline_ptr); + + if (err == KErrNotFound) { + // Strip path from program name and try again (i.e. try from default location "\sys\bin") + int index = programName.lastIndexOf(QDir::separator()); + int index2 = programName.lastIndexOf(QChar(QLatin1Char('/'))); + index = qMax(index, index2); + + if (index != -1 && programName.length() >= index) { + QString strippedName; + strippedName = programName.mid(index + 1); + QPROCESS_DEBUG_PRINT("qt_create_symbian_process() Executable '%s' not found, trying stripped version '%s'", + qPrintable(programName), qPrintable(strippedName)); + + TPtrC stripped_ptr(reinterpret_cast<const TText*>(strippedName.constData())); + err = newProc->Create(stripped_ptr, cmdline_ptr); + + if (err != KErrNone) { + QPROCESS_DEBUG_PRINT("qt_create_symbian_process() Unable to create process '%s': %d", + qPrintable(strippedName), err); + } + } + } + + if (err == KErrNone) + *proc = newProc; + else + delete newProc; + + return err; +} + +static qint64 qt_native_read(int fd, char *data, qint64 maxlen) +{ + qint64 ret = 0; + do { + ret = ::read(fd, data, maxlen); + } while (ret == -1 && errno == EINTR); + + QPROCESS_DEBUG_PRINT("qt_native_read(): fd: %d, result: %d, errno = %d", fd, (int)ret, errno); + + return ret; +} + +static qint64 qt_native_write(int fd, const char *data, qint64 len) +{ + qint64 ret = 0; + do { + ret = ::write(fd, data, len); + } while (ret == -1 && errno == EINTR); + + QPROCESS_DEBUG_PRINT("qt_native_write(): fd: %d, result: %d, errno = %d", fd, (int)ret, errno); + + return ret; +} + +static void qt_native_close(int fd) +{ + int ret; + do { + ret = ::close(fd); + } while (ret == -1 && errno == EINTR); +} + +static void qt_create_pipe(int *pipe) +{ + if (pipe[0] != -1) + qt_native_close(pipe[0]); + if (pipe[1] != -1) + qt_native_close(pipe[1]); + if (::pipe(pipe) != 0) { + qWarning("QProcessPrivate::createPipe: Cannot create pipe %p: %s", + pipe, qPrintable(qt_error_string(errno))); + } else { + QPROCESS_DEBUG_PRINT("qt_create_pipe(): Created pipe %d - %d", pipe[0], pipe[1]); + } +} + +// Called from ProcessManagerThread +QProcessActive *QProcessActive::construct(QProcess *process, + RProcess **proc, + int serial, + int deathPipe) +{ + QPROCESS_ASSERT((process || proc || *proc), + EProcessActiveNullParameter, + "QProcessActive::construct(): process (0x%x), proc (0x%x) or *proc == NULL, not creating an instance", process, proc) + + QProcessActive *newInstance = new QProcessActive(); + + if (!newInstance) { + QPROCESS_DEBUG_PRINT("QProcessActive::construct(): Failed to create new instance"); + } else { + newInstance->process = process; + newInstance->pproc = proc; + newInstance->serial = serial; + newInstance->deathPipe = deathPipe; + newInstance->errorValue = false; + } + + return newInstance; +} + +// Called from ProcessManagerThread +QProcessActive::QProcessActive() + : CActive(CActive::EPriorityStandard) +{ + // Nothing to do +} + +// Called from main thread +QProcessActive::~QProcessActive() +{ + process = NULL; + pproc = NULL; +} + +// Called from ProcessManagerThread +void QProcessActive::start() +{ + if (qt_rprocess_running(*pproc)) { + CActiveScheduler::Add(this); + (*pproc)->Logon(iStatus); + SetActive(); + QPROCESS_DEBUG_PRINT("QProcessActive::start(): Started monitoring for process exit."); + } else { + QPROCESS_DEBUG_PRINT("QProcessActive::start(): Process doesn't exist or is already dead"); + // Assume process has already died + qt_native_write(deathPipe, "", 1); + errorValue = true; + } +} + +// Called from ProcessManagerThread +void QProcessActive::stop() +{ + QPROCESS_DEBUG_PRINT("QProcessActive::stop()"); + + // Remove this from scheduler (also cancels the request) + Deque(); +} + +bool QProcessActive::error() +{ + return errorValue; +} + +// Called from ProcessManagerThread +void QProcessActive::RunL() +{ + // If this method gets executed, the monitored process has died + + // Notify main thread + qt_native_write(deathPipe, "", 1); + QPROCESS_DEBUG_PRINT("QProcessActive::RunL() sending death notice to %d", deathPipe); +} + +// Called from ProcessManagerThread +TInt QProcessActive::RunError(TInt aError) +{ + Q_UNUSED(aError); + // Handle RunL leave (should never happen) + QPROCESS_ASSERT(0, EProcessActiveRunError, "QProcessActive::RunError(): Should never get here!") + return 0; +} + +// Called from ProcessManagerThread +void QProcessActive::DoCancel() +{ + QPROCESS_DEBUG_PRINT("QProcessActive::DoCancel()"); + + if (qt_rprocess_running(*pproc)) { + (*pproc)->LogonCancel(iStatus); + QPROCESS_DEBUG_PRINT("QProcessActive::DoCancel(): Stopped monitoring for process exit."); + } else { + QPROCESS_DEBUG_PRINT("QProcessActive::DoCancel(): Process doesn't exist"); + } +} + + +// Called from ProcessManagerThread +QProcessManagerMediator *QProcessManagerMediator::construct() +{ + QProcessManagerMediator *newInstance = new QProcessManagerMediator; + TInt err(KErrNone); + + newInstance->currentCommand = ENoCommand; + newInstance->currentObserver = NULL; + + if (newInstance) { + err = newInstance->processManagerThread.Open(newInstance->processManagerThread.Id()); + QPROCESS_ASSERT((err == KErrNone), + EProcessManagerMediatorThreadOpenFailed, + "QProcessManagerMediator::construct(): Failed to open processManagerThread (err:%d)", err) + } else { + QPROCESS_ASSERT(0, + EProcessManagerMediatorCreationFailed, + "QProcessManagerMediator::construct(): Failed to open construct mediator") + } + + // Activate mediator + CActiveScheduler::Add(newInstance); + newInstance->iStatus = KRequestPending; + newInstance->SetActive(); + QPROCESS_DEBUG_PRINT("QProcessManagerMediator::construct(): new instance successfully created and activated"); + + return newInstance; +} + +// Called from ProcessManagerThread +QProcessManagerMediator::QProcessManagerMediator() + : CActive(CActive::EPriorityStandard) +{ + // Nothing to do +} + +// Called from main thread +QProcessManagerMediator::~QProcessManagerMediator() +{ + processManagerThread.Close(); + currentCommand = ENoCommand; + currentObserver = NULL; +} + +// Called from main thread +bool QProcessManagerMediator::add(QProcessActive *processObserver) +{ + QPROCESS_DEBUG_PRINT("QProcessManagerMediator::add()"); + return notify(processObserver, EAdd); +} + +// Called from main thread +void QProcessManagerMediator::remove(QProcessActive *processObserver) +{ + QPROCESS_DEBUG_PRINT("QProcessManagerMediator::remove()"); + notify(processObserver, ERemove); +} + +// Called from main thread +void QProcessManagerMediator::terminate() +{ + QPROCESS_DEBUG_PRINT("QProcessManagerMediator::terminate()"); + notify(NULL, ETerminate); +} + +// Called from main thread +bool QProcessManagerMediator::notify(QProcessActive *processObserver, Commands command) +{ + bool success(true); + + QPROCESS_DEBUG_PRINT("QProcessManagerMediator::Notify(): Command: %d, processObserver: 0x%x", command, processObserver); + + QPROCESS_ASSERT((command == ETerminate || processObserver), + EProcessManagerMediatorNullObserver, + "QProcessManagerMediator::Notify(): NULL processObserver not allowed for command: %d", command) + + QPROCESS_ASSERT(IsActive(), + EProcessManagerMediatorInactive, + "QProcessManagerMediator::Notify(): Mediator is not active!") + + QPROCESS_ASSERT(iStatus == KRequestPending, + EProcessManagerMediatorNotPending, + "QProcessManagerMediator::Notify(): Mediator request not pending!") + + currentObserver = processObserver; + currentCommand = command; + + // Sync with process manager thread + TRequestStatus pmStatus; + processManagerThread.Rendezvous(pmStatus); + + // Complete request -> RunL will run in the process manager thread + TRequestStatus *status = &iStatus; + processManagerThread.RequestComplete(status, command); + + QPROCESS_DEBUG_PRINT("QProcessManagerMediator::Notify(): Waiting process manager to complete..."); + User::WaitForRequest(pmStatus); + QPROCESS_DEBUG_PRINT("QProcessManagerMediator::Notify(): Wait over"); + + if (currentObserver) { + success = !(currentObserver->error()); + QPROCESS_DEBUG_PRINT("QProcessManagerMediator::Notify(): success = %d", success); + } + + currentObserver = NULL; + currentCommand = ENoCommand; + + return success; +} + +// Called from ProcessManagerThread +void QProcessManagerMediator::RunL() +{ + QPROCESS_DEBUG_PRINT("QProcessManagerMediator::RunL(): currentCommand: %d, iStatus: %d", currentCommand, iStatus.Int()); + switch (currentCommand) { + case EAdd: + currentObserver->start(); + break; + case ERemove: + currentObserver->stop(); + break; + case ETerminate: + Deque(); + CActiveScheduler::Stop(); + return; + default: + QPROCESS_ASSERT(0, + EProcessManagerMediatorInvalidCmd, + "QProcessManagerMediator::RunL(): Invalid command!") + break; + } + + iStatus = KRequestPending; + SetActive(); + + // Notify main thread that we are done + RThread::Rendezvous(KErrNone); +} + +// Called from ProcessManagerThread +TInt QProcessManagerMediator::RunError(TInt aError) +{ + Q_UNUSED(aError); + // Handle RunL leave (should never happen) + QPROCESS_ASSERT(0, + EProcessManagerMediatorRunError, + "QProcessManagerMediator::RunError(): Should never get here!") + return 0; +} + +// Called from ProcessManagerThread +void QProcessManagerMediator::DoCancel() +{ + QPROCESS_DEBUG_PRINT("QProcessManagerMediator::DoCancel()"); + TRequestStatus *status = &iStatus; + processManagerThread.RequestComplete(status, KErrCancel); +} + +Q_GLOBAL_STATIC(QProcessManager, processManager) + +TInt processManagerThreadFunction(TAny *param) +{ + QPROCESS_ASSERT(param, + EProcessManagerNullParam, + "processManagerThreadFunction(): NULL param") + + QProcessManager *manager = reinterpret_cast<QProcessManager*>(param); + + CActiveScheduler *scheduler = new CQtActiveScheduler(); + + QPROCESS_ASSERT(scheduler, + EProcessManagerSchedulerCreationFail, + "processManagerThreadFunction(): Scheduler creation failed") + + CActiveScheduler::Install(scheduler); + + //Creating mediator also adds it to scheduler and activates it. Failure will panic. + manager->setMediator(QProcessManagerMediator::construct()); + RThread::Rendezvous(KErrNone); + + CActiveScheduler::Start(); + + CActiveScheduler::Install(NULL); + delete scheduler; + + return KErrNone; +} + +QProcessManager::QProcessManager() + : mediator(NULL), threadStarted(false) +{ + TInt err = managerMutex.CreateLocal(); + + QPROCESS_ASSERT(err == KErrNone, + EProcessManagerMutexCreationFail, + "QProcessManager::QProcessManager(): Failed to create new managerMutex (err: %d)", err) +} + +QProcessManager::~QProcessManager() +{ + QPROCESS_DEBUG_PRINT("QProcessManager::~QProcessManager()"); + + // Check if manager thread is still alive. If this destructor is ran as part of global + // static cleanup, manager thread will most likely be terminated by kernel at this point, + // so trying to delete QProcessActives and QProcessMediators will panic as they + // will still be active. They can also no longer be canceled as the thread is already gone. + // In case manager thread has already died, we simply do nothing and let the deletion of + // the main heap at process exit take care of stray objects. + + if (managerThread.Handle() && managerThread.ExitType() == EExitPending) { + // Cancel death listening for all child processes + if (mediator) { + QMap<int, QProcessActive *>::Iterator it = children.begin(); + while (it != children.end()) { + // Remove all monitors + QProcessActive *active = it.value(); + mediator->remove(active); + + QPROCESS_DEBUG_PRINT("QProcessManager::~QProcessManager() removed listening for a process"); + ++it; + } + + // Terminate process manager thread. + mediator->terminate(); + delete mediator; + } + + qDeleteAll(children.values()); + children.clear(); + } + + managerThread.Close(); + managerMutex.Close(); +} + +void QProcessManager::startThread() +{ + lock(); + + if (!threadStarted) { + TInt err = managerThread.Create(KQProcessManagerThreadName, + processManagerThreadFunction, + 0x5000, + (RAllocator*)NULL, + (TAny*)this, + EOwnerProcess); + + QPROCESS_ASSERT(err == KErrNone, + EProcessManagerThreadCreationFail, + "QProcessManager::startThread(): Failed to create new managerThread (err:%d)", err) + + threadStarted = true; + + // Manager thread must start running before we continue, so sync with rendezvous + TRequestStatus status; + managerThread.Rendezvous(status); + managerThread.Resume(); + User::WaitForRequest(status); + } + + unlock(); +} + +static QBasicAtomicInt idCounter = Q_BASIC_ATOMIC_INITIALIZER(1); + +bool QProcessManager::add(QProcess *process) +{ + QPROCESS_ASSERT(process, + EProcessManagerNullParam, + "QProcessManager::add(): Failed to add QProcessActive to ProcessManager - NULL process") + + lock(); + + int serial = idCounter.fetchAndAddRelaxed(1); + process->d_func()->serial = serial; + + QPROCESS_DEBUG_PRINT("QProcessManager::add(): serial: %d, deathPipe: %d - %d, symbianProcess: 0x%x", serial, process->d_func()->deathPipe[0], process->d_func()->deathPipe[1], process->d_func()->symbianProcess); + + QProcessActive *newActive = + QProcessActive::construct(process, + &(process->d_func()->symbianProcess), + serial, + process->d_func()->deathPipe[1]); + + if (newActive) { + if (mediator->add(newActive)) { + children.insert(serial, newActive); + unlock(); + return true; + } else { + QPROCESS_DEBUG_PRINT("QProcessManager::add(): Failed to add QProcessActive to ProcessManager"); + delete newActive; + } + } + + unlock(); + + return false; +} + +void QProcessManager::remove(QProcess *process) +{ + QPROCESS_ASSERT(process, + EProcessManagerNullParam, + "QProcessManager::remove(): Failed to remove QProcessActive from ProcessManager - NULL process") + + lock(); + + int serial = process->d_func()->serial; + QProcessActive *active = children.value(serial); + if (!active) { + unlock(); + return; + } + + mediator->remove(active); + + children.remove(serial); + delete active; + + unlock(); +} + +void QProcessPrivate::destroyPipe(int *pipe) +{ + if (pipe[1] != -1) { + qt_native_close(pipe[1]); + pipe[1] = -1; + } + if (pipe[0] != -1) { + qt_native_close(pipe[0]); + pipe[0] = -1; + } +} + +bool QProcessPrivate::createChannel(Channel &channel) +{ + Q_UNUSED(channel); + // No channels used + return false; +} + +void QProcessPrivate::startProcess() +{ + Q_Q(QProcess); + + QPROCESS_DEBUG_PRINT("QProcessPrivate::startProcess()"); + + // Start the process (platform dependent) + q->setProcessState(QProcess::Starting); + + processManager()->startThread(); + + qt_create_pipe(deathPipe); + if (threadData->eventDispatcher) { + deathNotifier = new QSocketNotifier(deathPipe[0], + QSocketNotifier::Read, q); + QObject::connect(deathNotifier, SIGNAL(activated(int)), + q, SLOT(_q_processDied())); + } + + TInt err = qt_create_symbian_process(&symbianProcess, program, arguments, nativeArguments); + + if (err == KErrNone) { + pid = symbianProcess->Id().Id(); + + ::fcntl(deathPipe[0], F_SETFL, ::fcntl(deathPipe[0], F_GETFL) | O_NONBLOCK); + + if (!processManager()->add(q)) { + qWarning("QProcessPrivate::startProcess(): Failed to start monitoring for process death."); + err = KErrNoMemory; + } + } + + if (err != KErrNone) { + // Cleanup, report error and return + QPROCESS_DEBUG_PRINT("QProcessPrivate::startProcess() Process open failed, err: %d, '%s'", err, qPrintable(program)); + q->setProcessState(QProcess::NotRunning); + processError = QProcess::FailedToStart; + q->setErrorString(QLatin1String(QT_TRANSLATE_NOOP(QProcess, "Resource error (qt_create_symbian_process failure)"))); + emit q->error(processError); + cleanup(); + return; + } + + processLaunched = true; + + symbianProcess->Resume(); + + QPROCESS_DEBUG_PRINT("QProcessPrivate::startProcess(): this: 0x%x, pid: %ld", this, pid); + + // Notify child start + _q_startupNotification(); + +} + +bool QProcessPrivate::processStarted() +{ + QPROCESS_DEBUG_PRINT("QProcessPrivate::processStarted() == %s", processLaunched ? "true" : "false"); + + // Since we cannot get information whether process has actually been launched + // or not in Symbian, we need to fake it. Assume process is started if launch was + // successful. + + return processLaunched; +} + +qint64 QProcessPrivate::bytesAvailableFromStdout() const +{ + // In Symbian, stdout is not supported + return 0; +} + +qint64 QProcessPrivate::bytesAvailableFromStderr() const +{ + // In Symbian, stderr is not supported + return 0; +} + +qint64 QProcessPrivate::readFromStdout(char *data, qint64 maxlen) +{ + Q_UNUSED(data); + Q_UNUSED(maxlen); + // In Symbian, stdout is not supported + return 0; +} + +qint64 QProcessPrivate::readFromStderr(char *data, qint64 maxlen) +{ + Q_UNUSED(data); + Q_UNUSED(maxlen); + // In Symbian, stderr is not supported + return 0; +} + +qint64 QProcessPrivate::writeToStdin(const char *data, qint64 maxlen) +{ + Q_UNUSED(data); + Q_UNUSED(maxlen); + // In Symbian, stdin is not supported + return 0; +} + +void QProcessPrivate::terminateProcess() +{ + // Needs PowerMgmt capability if process has been started; will panic kern-exec 46 otherwise. + // Always works if process is not yet started. + if (qt_rprocess_running(symbianProcess)) { + symbianProcess->Terminate(0); + } else { + QPROCESS_DEBUG_PRINT("QProcessPrivate::terminateProcess(), Process not running"); + } +} + +void QProcessPrivate::killProcess() +{ + // Needs PowerMgmt capability if process has been started; will panic kern-exec 46 otherwise. + // Always works if process is not yet started. + if (qt_rprocess_running(symbianProcess)) { + symbianProcess->Kill(0); + } else { + QPROCESS_DEBUG_PRINT("QProcessPrivate::killProcess(), Process not running"); + } +} + +bool QProcessPrivate::waitForStarted(int msecs) +{ + Q_UNUSED(msecs); + // Since we can get no actual feedback from process beyond its death, + // assume that started has already been emitted if process has been launched + return processLaunched; +} + +bool QProcessPrivate::waitForReadyRead(int msecs) +{ + // Functionality not supported in Symbian + Q_UNUSED(msecs); + return false; +} + +bool QProcessPrivate::waitForBytesWritten(int msecs) +{ + // Functionality not supported in Symbian + Q_UNUSED(msecs); + return false; +} + +bool QProcessPrivate::waitForFinished(int msecs) +{ + Q_Q(QProcess); + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForFinished(%d)", msecs); + + TRequestStatus timerStatus = KErrNone; + TRequestStatus logonStatus = KErrNone; + bool timeoutOccurred = false; + + // Logon to process to observe its death + if (qt_rprocess_running(symbianProcess)) { + symbianProcess->Logon(logonStatus); + + if (msecs < 0) { + // If timeout is negative, there is no timeout + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForFinished() - Waiting (just logon)..."); + User::WaitForRequest(logonStatus); + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForFinished() - Wait completed"); + } else { + // Create timer + RTimer timer; + timer.CreateLocal(); + TTimeIntervalMicroSeconds32 interval(msecs*1000); + timer.After(timerStatus, interval); + + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForFinished() - Waiting (logon + timer)..."); + User::WaitForRequest(logonStatus, timerStatus); + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForFinished() - Wait completed"); + + if (logonStatus != KRequestPending) { + timer.Cancel(); + User::WaitForRequest(timerStatus); + } else { + timeoutOccurred = true; + symbianProcess->LogonCancel(logonStatus); + User::WaitForRequest(logonStatus); + } + timer.Close(); + } + } else { + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForFinished(), qt_rprocess_running returned false"); + } + + if (timeoutOccurred) { + processError = QProcess::Timedout; + q->setErrorString(QLatin1String(QT_TRANSLATE_NOOP(QProcess, "Process operation timed out"))); + return false; + } + + _q_processDied(); + + return true; +} + +bool QProcessPrivate::waitForWrite(int msecs) +{ + // Functionality not supported in Symbian + Q_UNUSED(msecs); + return false; +} + +// Deceptively named function. Exit code is actually got in waitForDeadChild(). +void QProcessPrivate::findExitCode() +{ + Q_Q(QProcess); + processManager()->remove(q); +} + +bool QProcessPrivate::waitForDeadChild() +{ + Q_Q(QProcess); + + // read a byte from the death pipe + char c; + qt_native_read(deathPipe[0], &c, 1); + + if (symbianProcess && symbianProcess->Handle()) { + TExitType et = symbianProcess->ExitType(); + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForDeadChild() symbianProcess->ExitType: %d", et); + if (et != EExitPending) { + processManager()->remove(q); + exitCode = symbianProcess->ExitReason(); + crashed = (et == EExitPanic); +#if defined QPROCESS_DEBUG + TExitCategoryName catName = symbianProcess->ExitCategory(); + qDebug() << "QProcessPrivate::waitForDeadChild() dead with exitCode" + << exitCode << ", crashed:" << crashed + << ", category:" << QString((const QChar *)catName.Ptr()); +#endif + } else { + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForDeadChild() not dead!"); + } + } + + return true; +} + +void QProcessPrivate::_q_notified() +{ + // Nothing to do in Symbian +} + +bool QProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid) +{ + QPROCESS_DEBUG_PRINT("QProcessPrivate::startDetached()"); + Q_UNUSED(workingDirectory); + + RProcess *newProc = NULL; + + TInt err = qt_create_symbian_process(&newProc, program, arguments, QString()); + + if (err == KErrNone) { + if (pid) + *pid = newProc->Id().Id(); + + newProc->Resume(); + newProc->Close(); + delete newProc; + return true; + } + + return false; +} + + +void QProcessPrivate::initializeProcessManager() +{ + (void) processManager(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_PROCESS diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp new file mode 100644 index 0000000000..3af9b46df8 --- /dev/null +++ b/src/corelib/io/qprocess_unix.cpp @@ -0,0 +1,1297 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QPROCESS_DEBUG +#include "qdebug.h" + +#ifndef QT_NO_PROCESS + +#if defined QPROCESS_DEBUG +#include "qstring.h" +#include <ctype.h> + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +QT_BEGIN_NAMESPACE +static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(c)) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1(); + } + } + + if (len < maxSize) + out += "..."; + + return out; +} +QT_END_NAMESPACE +#endif + +#include "qplatformdefs.h" + +#include "qprocess.h" +#include "qprocess_p.h" +#include "private/qcore_unix_p.h" + +#ifdef Q_OS_MAC +#include <private/qcore_mac_p.h> +#endif + +#include <private/qcoreapplication_p.h> +#include <private/qthread_p.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qlist.h> +#include <qhash.h> +#include <qmutex.h> +#include <qsemaphore.h> +#include <qsocketnotifier.h> +#include <qthread.h> +#include <qelapsedtimer.h> + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +QT_BEGIN_NAMESPACE + +// POSIX requires PIPE_BUF to be 512 or larger +// so we will use 512 +static const int errorBufferMax = 512; + +#ifdef Q_OS_INTEGRITY +static inline char *strdup(const char *data) +{ + return qstrdup(data); +} +#endif + +static int qt_qprocess_deadChild_pipe[2]; +static struct sigaction qt_sa_old_sigchld_handler; +static void qt_sa_sigchld_handler(int signum) +{ + qt_safe_write(qt_qprocess_deadChild_pipe[1], "", 1); +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "*** SIGCHLD\n"); +#endif + + // load it as volatile + void (*oldAction)(int) = ((volatile struct sigaction *)&qt_sa_old_sigchld_handler)->sa_handler; + if (oldAction && oldAction != SIG_IGN) + oldAction(signum); +} + +static inline void add_fd(int &nfds, int fd, fd_set *fdset) +{ + FD_SET(fd, fdset); + if ((fd) > nfds) + nfds = fd; +} + +struct QProcessInfo { + QProcess *process; + int deathPipe; + int exitResult; + pid_t pid; + int serialNumber; +}; + +class QProcessManager : public QThread +{ + Q_OBJECT +public: + QProcessManager(); + ~QProcessManager(); + + void run(); + void catchDeadChildren(); + void add(pid_t pid, QProcess *process); + void remove(QProcess *process); + void lock(); + void unlock(); + +private: + QMutex mutex; + QHash<int, QProcessInfo *> children; +}; + + +Q_GLOBAL_STATIC(QMutex, processManagerGlobalMutex) + +static QProcessManager *processManager() { + // The constructor of QProcessManager should be called only once + // so we cannot use Q_GLOBAL_STATIC directly for QProcessManager + QMutex *mutex = processManagerGlobalMutex(); + QMutexLocker locker(mutex); + static QProcessManager processManager; + return &processManager; +} + +QProcessManager::QProcessManager() +{ +#if defined (QPROCESS_DEBUG) + qDebug() << "QProcessManager::QProcessManager()"; +#endif + // initialize the dead child pipe and make it non-blocking. in the + // extremely unlikely event that the pipe fills up, we do not under any + // circumstances want to block. + qt_safe_pipe(qt_qprocess_deadChild_pipe, O_NONBLOCK); + + // set up the SIGCHLD handler, which writes a single byte to the dead + // child pipe every time a child dies. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = qt_sa_sigchld_handler; + action.sa_flags = SA_NOCLDSTOP; + ::sigaction(SIGCHLD, &action, &qt_sa_old_sigchld_handler); +} + +QProcessManager::~QProcessManager() +{ + // notify the thread that we're shutting down. + qt_safe_write(qt_qprocess_deadChild_pipe[1], "@", 1); + qt_safe_close(qt_qprocess_deadChild_pipe[1]); + wait(); + + // on certain unixes, closing the reading end of the pipe will cause + // select in run() to block forever, rather than return with EBADF. + qt_safe_close(qt_qprocess_deadChild_pipe[0]); + + qt_qprocess_deadChild_pipe[0] = -1; + qt_qprocess_deadChild_pipe[1] = -1; + + qDeleteAll(children.values()); + children.clear(); + + struct sigaction currentAction; + ::sigaction(SIGCHLD, 0, ¤tAction); + if (currentAction.sa_handler == qt_sa_sigchld_handler) { + ::sigaction(SIGCHLD, &qt_sa_old_sigchld_handler, 0); + } +} + +void QProcessManager::run() +{ + forever { + fd_set readset; + FD_ZERO(&readset); + FD_SET(qt_qprocess_deadChild_pipe[0], &readset); + +#if defined (QPROCESS_DEBUG) + qDebug() << "QProcessManager::run() waiting for children to die"; +#endif + + // block forever, or until activity is detected on the dead child + // pipe. the only other peers are the SIGCHLD signal handler, and the + // QProcessManager destructor. + int nselect = select(qt_qprocess_deadChild_pipe[0] + 1, &readset, 0, 0, 0); + if (nselect < 0) { + if (errno == EINTR) + continue; + break; + } + + // empty only one byte from the pipe, even though several SIGCHLD + // signals may have been delivered in the meantime, to avoid race + // conditions. + char c; + if (qt_safe_read(qt_qprocess_deadChild_pipe[0], &c, 1) < 0 || c == '@') + break; + + // catch any and all children that we can. + catchDeadChildren(); + } +} + +void QProcessManager::catchDeadChildren() +{ + QMutexLocker locker(&mutex); + + // try to catch all children whose pid we have registered, and whose + // deathPipe is still valid (i.e, we have not already notified it). + QHash<int, QProcessInfo *>::Iterator it = children.begin(); + while (it != children.end()) { + // notify all children that they may have died. they need to run + // waitpid() in their own thread. + QProcessInfo *info = it.value(); + qt_safe_write(info->deathPipe, "", 1); + +#if defined (QPROCESS_DEBUG) + qDebug() << "QProcessManager::run() sending death notice to" << info->process; +#endif + ++it; + } +} + +static QBasicAtomicInt idCounter = Q_BASIC_ATOMIC_INITIALIZER(1); + +void QProcessManager::add(pid_t pid, QProcess *process) +{ +#if defined (QPROCESS_DEBUG) + qDebug() << "QProcessManager::add() adding pid" << pid << "process" << process; +#endif + + // insert a new info structure for this process + QProcessInfo *info = new QProcessInfo; + info->process = process; + info->deathPipe = process->d_func()->deathPipe[1]; + info->exitResult = 0; + info->pid = pid; + + int serial = idCounter.fetchAndAddRelaxed(1); + process->d_func()->serial = serial; + children.insert(serial, info); +} + +void QProcessManager::remove(QProcess *process) +{ + QMutexLocker locker(&mutex); + + int serial = process->d_func()->serial; + QProcessInfo *info = children.take(serial); +#if defined (QPROCESS_DEBUG) + if (info) + qDebug() << "QProcessManager::remove() removing pid" << info->pid << "process" << info->process; +#endif + delete info; +} + +void QProcessManager::lock() +{ + mutex.lock(); +} + +void QProcessManager::unlock() +{ + mutex.unlock(); +} + +static void qt_create_pipe(int *pipe) +{ + if (pipe[0] != -1) + qt_safe_close(pipe[0]); + if (pipe[1] != -1) + qt_safe_close(pipe[1]); + if (qt_safe_pipe(pipe) != 0) { + qWarning("QProcessPrivate::createPipe: Cannot create pipe %p: %s", + pipe, qPrintable(qt_error_string(errno))); + } +} + +void QProcessPrivate::destroyPipe(int *pipe) +{ + if (pipe[1] != -1) { + qt_safe_close(pipe[1]); + pipe[1] = -1; + } + if (pipe[0] != -1) { + qt_safe_close(pipe[0]); + pipe[0] = -1; + } +} + +/* + Create the pipes to a QProcessPrivate::Channel. + + This function must be called in order: stdin, stdout, stderr +*/ +bool QProcessPrivate::createChannel(Channel &channel) +{ + Q_Q(QProcess); + + if (&channel == &stderrChannel && processChannelMode == QProcess::MergedChannels) { + channel.pipe[0] = -1; + channel.pipe[1] = -1; + return true; + } + + if (channel.type == Channel::Normal) { + // we're piping this channel to our own process + qt_create_pipe(channel.pipe); + + // create the socket notifiers + if (threadData->eventDispatcher) { + if (&channel == &stdinChannel) { + channel.notifier = new QSocketNotifier(channel.pipe[1], + QSocketNotifier::Write, q); + channel.notifier->setEnabled(false); + QObject::connect(channel.notifier, SIGNAL(activated(int)), + q, SLOT(_q_canWrite())); + } else { + channel.notifier = new QSocketNotifier(channel.pipe[0], + QSocketNotifier::Read, q); + const char *receiver; + if (&channel == &stdoutChannel) + receiver = SLOT(_q_canReadStandardOutput()); + else + receiver = SLOT(_q_canReadStandardError()); + QObject::connect(channel.notifier, SIGNAL(activated(int)), + q, receiver); + } + } + + return true; + } else if (channel.type == Channel::Redirect) { + // we're redirecting the channel to/from a file + QByteArray fname = QFile::encodeName(channel.file); + + if (&channel == &stdinChannel) { + // try to open in read-only mode + channel.pipe[1] = -1; + if ( (channel.pipe[0] = qt_safe_open(fname, O_RDONLY)) != -1) + return true; // success + + q->setErrorString(QProcess::tr("Could not open input redirection for reading")); + } else { + int mode = O_WRONLY | O_CREAT; + if (channel.append) + mode |= O_APPEND; + else + mode |= O_TRUNC; + + channel.pipe[0] = -1; + if ( (channel.pipe[1] = qt_safe_open(fname, mode, 0666)) != -1) + return true; // success + + q->setErrorString(QProcess::tr("Could not open output redirection for writing")); + } + + // could not open file + processError = QProcess::FailedToStart; + emit q->error(processError); + cleanup(); + return false; + } else { + Q_ASSERT_X(channel.process, "QProcess::start", "Internal error"); + + Channel *source; + Channel *sink; + + if (channel.type == Channel::PipeSource) { + // we are the source + source = &channel; + sink = &channel.process->stdinChannel; + + Q_ASSERT(source == &stdoutChannel); + Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink); + } else { + // we are the sink; + source = &channel.process->stdoutChannel; + sink = &channel; + + Q_ASSERT(sink == &stdinChannel); + Q_ASSERT(source->process == this && source->type == Channel::PipeSource); + } + + if (source->pipe[1] != INVALID_Q_PIPE || sink->pipe[0] != INVALID_Q_PIPE) { + // already created, do nothing + return true; + } else { + Q_ASSERT(source->pipe[0] == INVALID_Q_PIPE && source->pipe[1] == INVALID_Q_PIPE); + Q_ASSERT(sink->pipe[0] == INVALID_Q_PIPE && sink->pipe[1] == INVALID_Q_PIPE); + + Q_PIPE pipe[2] = { -1, -1 }; + qt_create_pipe(pipe); + sink->pipe[0] = pipe[0]; + source->pipe[1] = pipe[1]; + + return true; + } + } +} + +static char **_q_dupEnvironment(const QHash<QByteArray, QByteArray> &environment, int *envc) +{ + *envc = 0; + if (environment.isEmpty()) + return 0; + + // if LD_LIBRARY_PATH exists in the current environment, but + // not in the environment list passed by the programmer, then + // copy it over. +#if defined(Q_OS_MAC) + static const char libraryPath[] = "DYLD_LIBRARY_PATH"; +#else + static const char libraryPath[] = "LD_LIBRARY_PATH"; +#endif + const QByteArray envLibraryPath = qgetenv(libraryPath); + bool needToAddLibraryPath = !envLibraryPath.isEmpty() && + !environment.contains(libraryPath); + + char **envp = new char *[environment.count() + 2]; + envp[environment.count()] = 0; + envp[environment.count() + 1] = 0; + + QHash<QByteArray, QByteArray>::ConstIterator it = environment.constBegin(); + const QHash<QByteArray, QByteArray>::ConstIterator end = environment.constEnd(); + for ( ; it != end; ++it) { + QByteArray key = it.key(); + QByteArray value = it.value(); + key.reserve(key.length() + 1 + value.length()); + key.append('='); + key.append(value); + + envp[(*envc)++] = ::strdup(key.constData()); + } + + if (needToAddLibraryPath) + envp[(*envc)++] = ::strdup(QByteArray(QByteArray(libraryPath) + '=' + + envLibraryPath).constData()); + return envp; +} + +// under QNX RTOS we have to use vfork() when multithreading +inline pid_t qt_fork() +{ +#if defined(Q_OS_QNX) + return vfork(); +#else + return fork(); +#endif +} + +#ifdef Q_OS_MAC +Q_GLOBAL_STATIC(QMutex, cfbundleMutex); +#endif + +void QProcessPrivate::startProcess() +{ + Q_Q(QProcess); + +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::startProcess()"); +#endif + + processManager()->start(); + + // Initialize pipes + if (!createChannel(stdinChannel) || + !createChannel(stdoutChannel) || + !createChannel(stderrChannel)) + return; + qt_create_pipe(childStartedPipe); + qt_create_pipe(deathPipe); + + if (threadData->eventDispatcher) { + startupSocketNotifier = new QSocketNotifier(childStartedPipe[0], + QSocketNotifier::Read, q); + QObject::connect(startupSocketNotifier, SIGNAL(activated(int)), + q, SLOT(_q_startupNotification())); + + deathNotifier = new QSocketNotifier(deathPipe[0], + QSocketNotifier::Read, q); + QObject::connect(deathNotifier, SIGNAL(activated(int)), + q, SLOT(_q_processDied())); + } + + // Start the process (platform dependent) + q->setProcessState(QProcess::Starting); + + // Create argument list with right number of elements, and set the final + // one to 0. + char **argv = new char *[arguments.count() + 2]; + argv[arguments.count() + 1] = 0; + + // Encode the program name. + QByteArray encodedProgramName = QFile::encodeName(program); +#ifdef Q_OS_MAC + // allow invoking of .app bundles on the Mac. + QFileInfo fileInfo(QString::fromUtf8(encodedProgramName.constData())); + if (encodedProgramName.endsWith(".app") && fileInfo.isDir()) { + QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, + QCFString(fileInfo.absoluteFilePath()), + kCFURLPOSIXPathStyle, true); + { + // CFBundle is not reentrant, since CFBundleCreate might return a reference + // to a cached bundle object. Protect the bundle calls with a mutex lock. + QMutexLocker lock(cfbundleMutex()); + QCFType<CFBundleRef> bundle = CFBundleCreate(0, url); + url = CFBundleCopyExecutableURL(bundle); + } + if (url) { + QCFString str = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); + encodedProgramName += "/Contents/MacOS/" + static_cast<QString>(str).toUtf8(); + } + } +#endif + + // Add the program name to the argument list. + char *dupProgramName = ::strdup(encodedProgramName.constData()); + argv[0] = dupProgramName; + + // Add every argument to the list + for (int i = 0; i < arguments.count(); ++i) { + QString arg = arguments.at(i); +#ifdef Q_OS_MAC + // Mac OS X uses UTF8 for exec, regardless of the system locale. + argv[i + 1] = ::strdup(arg.toUtf8().constData()); +#else + argv[i + 1] = ::strdup(arg.toLocal8Bit().constData()); +#endif + } + + // Duplicate the environment. + int envc = 0; + char **envp = 0; + if (environment.d.constData()) + envp = _q_dupEnvironment(environment.d.constData()->hash, &envc); + + // Encode the working directory if it's non-empty, otherwise just pass 0. + const char *workingDirPtr = 0; + QByteArray encodedWorkingDirectory; + if (!workingDirectory.isEmpty()) { + encodedWorkingDirectory = QFile::encodeName(workingDirectory); + workingDirPtr = encodedWorkingDirectory.constData(); + } + + // If the program does not specify a path, generate a list of possible + // locations for the binary using the PATH environment variable. + char **path = 0; + int pathc = 0; + if (!program.contains(QLatin1Char('/'))) { + const QString pathEnv = QString::fromLocal8Bit(::getenv("PATH")); + if (!pathEnv.isEmpty()) { + QStringList pathEntries = pathEnv.split(QLatin1Char(':'), QString::SkipEmptyParts); + if (!pathEntries.isEmpty()) { + pathc = pathEntries.size(); + path = new char *[pathc + 1]; + path[pathc] = 0; + + for (int k = 0; k < pathEntries.size(); ++k) { + QByteArray tmp = QFile::encodeName(pathEntries.at(k)); + if (!tmp.endsWith('/')) tmp += '/'; + tmp += encodedProgramName; + path[k] = ::strdup(tmp.constData()); + } + } + } + } + + // Start the process manager, and fork off the child process. + processManager()->lock(); + pid_t childPid = qt_fork(); + int lastForkErrno = errno; + if (childPid != 0) { + // Clean up duplicated memory. + free(dupProgramName); + for (int i = 1; i <= arguments.count(); ++i) + free(argv[i]); + for (int i = 0; i < envc; ++i) + free(envp[i]); + for (int i = 0; i < pathc; ++i) + free(path[i]); + delete [] argv; + delete [] envp; + delete [] path; + } + if (childPid < 0) { + // Cleanup, report error and return +#if defined (QPROCESS_DEBUG) + qDebug("qt_fork failed: %s", qPrintable(qt_error_string(lastForkErrno))); +#endif + processManager()->unlock(); + q->setProcessState(QProcess::NotRunning); + processError = QProcess::FailedToStart; + q->setErrorString(QProcess::tr("Resource error (fork failure): %1").arg(qt_error_string(lastForkErrno))); + emit q->error(processError); + cleanup(); + return; + } + + // Start the child. + if (childPid == 0) { + execChild(workingDirPtr, path, argv, envp); + ::_exit(-1); + } + + // Register the child. In the mean time, we can get a SIGCHLD, so we need + // to keep the lock held to avoid a race to catch the child. + processManager()->add(childPid, q); + pid = Q_PID(childPid); + processManager()->unlock(); + + // parent + // close the ends we don't use and make all pipes non-blocking + ::fcntl(deathPipe[0], F_SETFL, ::fcntl(deathPipe[0], F_GETFL) | O_NONBLOCK); + qt_safe_close(childStartedPipe[1]); + childStartedPipe[1] = -1; + + if (stdinChannel.pipe[0] != -1) { + qt_safe_close(stdinChannel.pipe[0]); + stdinChannel.pipe[0] = -1; + } + + if (stdinChannel.pipe[1] != -1) + ::fcntl(stdinChannel.pipe[1], F_SETFL, ::fcntl(stdinChannel.pipe[1], F_GETFL) | O_NONBLOCK); + + if (stdoutChannel.pipe[1] != -1) { + qt_safe_close(stdoutChannel.pipe[1]); + stdoutChannel.pipe[1] = -1; + } + + if (stdoutChannel.pipe[0] != -1) + ::fcntl(stdoutChannel.pipe[0], F_SETFL, ::fcntl(stdoutChannel.pipe[0], F_GETFL) | O_NONBLOCK); + + if (stderrChannel.pipe[1] != -1) { + qt_safe_close(stderrChannel.pipe[1]); + stderrChannel.pipe[1] = -1; + } + if (stderrChannel.pipe[0] != -1) + ::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK); +} + +void QProcessPrivate::execChild(const char *workingDir, char **path, char **argv, char **envp) +{ + ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored + + Q_Q(QProcess); + + // copy the stdin socket (without closing on exec) + qt_safe_dup2(stdinChannel.pipe[0], fileno(stdin), 0); + + // copy the stdout and stderr if asked to + if (processChannelMode != QProcess::ForwardedChannels) { + qt_safe_dup2(stdoutChannel.pipe[1], fileno(stdout), 0); + + // merge stdout and stderr if asked to + if (processChannelMode == QProcess::MergedChannels) { + qt_safe_dup2(fileno(stdout), fileno(stderr), 0); + } else { + qt_safe_dup2(stderrChannel.pipe[1], fileno(stderr), 0); + } + } + + // make sure this fd is closed if execvp() succeeds + qt_safe_close(childStartedPipe[0]); + + // enter the working directory + if (workingDir) + QT_CHDIR(workingDir); + + // this is a virtual call, and it base behavior is to do nothing. + q->setupChildProcess(); + + // execute the process + if (!envp) { + qt_safe_execvp(argv[0], argv); + } else { + if (path) { + char **arg = path; + while (*arg) { + argv[0] = *arg; +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "QProcessPrivate::execChild() searching / starting %s\n", argv[0]); +#endif + qt_safe_execve(argv[0], argv, envp); + ++arg; + } + } else { +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "QProcessPrivate::execChild() starting %s\n", argv[0]); +#endif + qt_safe_execve(argv[0], argv, envp); + } + } + + // notify failure + QString error = qt_error_string(errno); +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "QProcessPrivate::execChild() failed (%s), notifying parent process\n", qPrintable(error)); +#endif + qt_safe_write(childStartedPipe[1], error.data(), error.length() * sizeof(QChar)); + qt_safe_close(childStartedPipe[1]); + childStartedPipe[1] = -1; +} + +bool QProcessPrivate::processStarted() +{ + ushort buf[errorBufferMax]; + int i = qt_safe_read(childStartedPipe[0], &buf, sizeof buf); + if (startupSocketNotifier) { + startupSocketNotifier->setEnabled(false); + startupSocketNotifier->deleteLater(); + startupSocketNotifier = 0; + } + qt_safe_close(childStartedPipe[0]); + childStartedPipe[0] = -1; + +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::processStarted() == %s", i <= 0 ? "true" : "false"); +#endif + + // did we read an error message? + if (i > 0) + q_func()->setErrorString(QString((const QChar *)buf, i / sizeof(QChar))); + + return i <= 0; +} + +qint64 QProcessPrivate::bytesAvailableFromStdout() const +{ + int nbytes = 0; + qint64 available = 0; + if (::ioctl(stdoutChannel.pipe[0], FIONREAD, (char *) &nbytes) >= 0) + available = (qint64) nbytes; +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::bytesAvailableFromStdout() == %lld", available); +#endif + return available; +} + +qint64 QProcessPrivate::bytesAvailableFromStderr() const +{ + int nbytes = 0; + qint64 available = 0; + if (::ioctl(stderrChannel.pipe[0], FIONREAD, (char *) &nbytes) >= 0) + available = (qint64) nbytes; +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::bytesAvailableFromStderr() == %lld", available); +#endif + return available; +} + +qint64 QProcessPrivate::readFromStdout(char *data, qint64 maxlen) +{ + qint64 bytesRead = qt_safe_read(stdoutChannel.pipe[0], data, maxlen); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::readFromStdout(%p \"%s\", %lld) == %lld", + data, qt_prettyDebug(data, bytesRead, 16).constData(), maxlen, bytesRead); +#endif + return bytesRead; +} + +qint64 QProcessPrivate::readFromStderr(char *data, qint64 maxlen) +{ + qint64 bytesRead = qt_safe_read(stderrChannel.pipe[0], data, maxlen); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::readFromStderr(%p \"%s\", %lld) == %lld", + data, qt_prettyDebug(data, bytesRead, 16).constData(), maxlen, bytesRead); +#endif + return bytesRead; +} + +static void qt_ignore_sigpipe() +{ + // Set to ignore SIGPIPE once only. + static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0); + if (atom.testAndSetRelaxed(0, 1)) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + ::sigaction(SIGPIPE, &noaction, 0); + } +} + +qint64 QProcessPrivate::writeToStdin(const char *data, qint64 maxlen) +{ + qt_ignore_sigpipe(); + + qint64 written = qt_safe_write(stdinChannel.pipe[1], data, maxlen); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::writeToStdin(%p \"%s\", %lld) == %lld", + data, qt_prettyDebug(data, maxlen, 16).constData(), maxlen, written); + if (written == -1) + qDebug("QProcessPrivate::writeToStdin(), failed to write (%s)", qPrintable(qt_error_string(errno))); +#endif + // If the O_NONBLOCK flag is set and If some data can be written without blocking + // the process, write() will transfer what it can and return the number of bytes written. + // Otherwise, it will return -1 and set errno to EAGAIN + if (written == -1 && errno == EAGAIN) + written = 0; + return written; +} + +void QProcessPrivate::terminateProcess() +{ +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::killProcess()"); +#endif + if (pid) + ::kill(pid_t(pid), SIGTERM); +} + +void QProcessPrivate::killProcess() +{ +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::killProcess()"); +#endif + if (pid) + ::kill(pid_t(pid), SIGKILL); +} + +static int select_msecs(int nfds, fd_set *fdread, fd_set *fdwrite, int timeout) +{ + if (timeout < 0) + return qt_safe_select(nfds, fdread, fdwrite, 0, 0); + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + return qt_safe_select(nfds, fdread, fdwrite, 0, &tv); +} + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +bool QProcessPrivate::waitForStarted(int msecs) +{ + Q_Q(QProcess); + +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::waitForStarted(%d) waiting for child to start (fd = %d)", msecs, + childStartedPipe[0]); +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(childStartedPipe[0], &fds); + if (select_msecs(childStartedPipe[0] + 1, &fds, 0, msecs) == 0) { + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::waitForStarted(%d) == false (timed out)", msecs); +#endif + return false; + } + + bool startedEmitted = _q_startupNotification(); +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::waitForStarted() == %s", startedEmitted ? "true" : "false"); +#endif + return startedEmitted; +} + +bool QProcessPrivate::waitForReadyRead(int msecs) +{ + Q_Q(QProcess); +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::waitForReadyRead(%d)", msecs); +#endif + + QElapsedTimer stopWatch; + stopWatch.start(); + + forever { + fd_set fdread; + fd_set fdwrite; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + + int nfds = deathPipe[0]; + FD_SET(deathPipe[0], &fdread); + + if (processState == QProcess::Starting) + add_fd(nfds, childStartedPipe[0], &fdread); + + if (stdoutChannel.pipe[0] != -1) + add_fd(nfds, stdoutChannel.pipe[0], &fdread); + if (stderrChannel.pipe[0] != -1) + add_fd(nfds, stderrChannel.pipe[0], &fdread); + + if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1) + add_fd(nfds, stdinChannel.pipe[1], &fdwrite); + + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + int ret = select_msecs(nfds + 1, &fdread, &fdwrite, timeout); + if (ret < 0) { + break; + } + if (ret == 0) { + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; + } + + if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) { + if (!_q_startupNotification()) + return false; + } + + bool readyReadEmitted = false; + if (stdoutChannel.pipe[0] != -1 && FD_ISSET(stdoutChannel.pipe[0], &fdread)) { + bool canRead = _q_canReadStandardOutput(); + if (processChannel == QProcess::StandardOutput && canRead) + readyReadEmitted = true; + } + if (stderrChannel.pipe[0] != -1 && FD_ISSET(stderrChannel.pipe[0], &fdread)) { + bool canRead = _q_canReadStandardError(); + if (processChannel == QProcess::StandardError && canRead) + readyReadEmitted = true; + } + if (readyReadEmitted) + return true; + + if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite)) + _q_canWrite(); + + if (deathPipe[0] == -1 || FD_ISSET(deathPipe[0], &fdread)) { + if (_q_processDied()) + return false; + } + } + return false; +} + +bool QProcessPrivate::waitForBytesWritten(int msecs) +{ + Q_Q(QProcess); +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::waitForBytesWritten(%d)", msecs); +#endif + + QElapsedTimer stopWatch; + stopWatch.start(); + + while (!writeBuffer.isEmpty()) { + fd_set fdread; + fd_set fdwrite; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + + int nfds = deathPipe[0]; + FD_SET(deathPipe[0], &fdread); + + if (processState == QProcess::Starting) + add_fd(nfds, childStartedPipe[0], &fdread); + + if (stdoutChannel.pipe[0] != -1) + add_fd(nfds, stdoutChannel.pipe[0], &fdread); + if (stderrChannel.pipe[0] != -1) + add_fd(nfds, stderrChannel.pipe[0], &fdread); + + + if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1) + add_fd(nfds, stdinChannel.pipe[1], &fdwrite); + + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + int ret = select_msecs(nfds + 1, &fdread, &fdwrite, timeout); + if (ret < 0) { + break; + } + + if (ret == 0) { + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; + } + + if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) { + if (!_q_startupNotification()) + return false; + } + + if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite)) + return _q_canWrite(); + + if (stdoutChannel.pipe[0] != -1 && FD_ISSET(stdoutChannel.pipe[0], &fdread)) + _q_canReadStandardOutput(); + + if (stderrChannel.pipe[0] != -1 && FD_ISSET(stderrChannel.pipe[0], &fdread)) + _q_canReadStandardError(); + + if (deathPipe[0] == -1 || FD_ISSET(deathPipe[0], &fdread)) { + if (_q_processDied()) + return false; + } + } + + return false; +} + +bool QProcessPrivate::waitForFinished(int msecs) +{ + Q_Q(QProcess); +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::waitForFinished(%d)", msecs); +#endif + + QElapsedTimer stopWatch; + stopWatch.start(); + + forever { + fd_set fdread; + fd_set fdwrite; + int nfds = -1; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + + if (processState == QProcess::Starting) + add_fd(nfds, childStartedPipe[0], &fdread); + + if (stdoutChannel.pipe[0] != -1) + add_fd(nfds, stdoutChannel.pipe[0], &fdread); + if (stderrChannel.pipe[0] != -1) + add_fd(nfds, stderrChannel.pipe[0], &fdread); + + if (processState == QProcess::Running) + add_fd(nfds, deathPipe[0], &fdread); + + if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1) + add_fd(nfds, stdinChannel.pipe[1], &fdwrite); + + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + int ret = select_msecs(nfds + 1, &fdread, &fdwrite, timeout); + if (ret < 0) { + break; + } + if (ret == 0) { + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; + } + + if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) { + if (!_q_startupNotification()) + return false; + } + if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite)) + _q_canWrite(); + + if (stdoutChannel.pipe[0] != -1 && FD_ISSET(stdoutChannel.pipe[0], &fdread)) + _q_canReadStandardOutput(); + + if (stderrChannel.pipe[0] != -1 && FD_ISSET(stderrChannel.pipe[0], &fdread)) + _q_canReadStandardError(); + + if (deathPipe[0] == -1 || FD_ISSET(deathPipe[0], &fdread)) { + if (_q_processDied()) + return true; + } + } + return false; +} + +bool QProcessPrivate::waitForWrite(int msecs) +{ + fd_set fdwrite; + FD_ZERO(&fdwrite); + FD_SET(stdinChannel.pipe[1], &fdwrite); + return select_msecs(stdinChannel.pipe[1] + 1, 0, &fdwrite, msecs < 0 ? 0 : msecs) == 1; +} + +void QProcessPrivate::findExitCode() +{ + Q_Q(QProcess); + processManager()->remove(q); +} + +bool QProcessPrivate::waitForDeadChild() +{ + Q_Q(QProcess); + + // read a byte from the death pipe + char c; + qt_safe_read(deathPipe[0], &c, 1); + + // check if our process is dead + int exitStatus; + if (qt_safe_waitpid(pid_t(pid), &exitStatus, WNOHANG) > 0) { + processManager()->remove(q); + crashed = !WIFEXITED(exitStatus); + exitCode = WEXITSTATUS(exitStatus); +#if defined QPROCESS_DEBUG + qDebug() << "QProcessPrivate::waitForDeadChild() dead with exitCode" + << exitCode << ", crashed?" << crashed; +#endif + return true; + } +#if defined QPROCESS_DEBUG + qDebug() << "QProcessPrivate::waitForDeadChild() not dead!"; +#endif + return false; +} + +void QProcessPrivate::_q_notified() +{ +} + +bool QProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid) +{ + processManager()->start(); + + QByteArray encodedWorkingDirectory = QFile::encodeName(workingDirectory); + + // To catch the startup of the child + int startedPipe[2]; + qt_safe_pipe(startedPipe); + // To communicate the pid of the child + int pidPipe[2]; + qt_safe_pipe(pidPipe); + + pid_t childPid = qt_fork(); + if (childPid == 0) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + ::sigaction(SIGPIPE, &noaction, 0); + + ::setsid(); + + qt_safe_close(startedPipe[0]); + qt_safe_close(pidPipe[0]); + + pid_t doubleForkPid = qt_fork(); + if (doubleForkPid == 0) { + qt_safe_close(pidPipe[1]); + + if (!encodedWorkingDirectory.isEmpty()) + QT_CHDIR(encodedWorkingDirectory.constData()); + + char **argv = new char *[arguments.size() + 2]; + for (int i = 0; i < arguments.size(); ++i) { +#ifdef Q_OS_MAC + argv[i + 1] = ::strdup(arguments.at(i).toUtf8().constData()); +#else + argv[i + 1] = ::strdup(arguments.at(i).toLocal8Bit().constData()); +#endif + } + argv[arguments.size() + 1] = 0; + + if (!program.contains(QLatin1Char('/'))) { + const QString path = QString::fromLocal8Bit(::getenv("PATH")); + if (!path.isEmpty()) { + QStringList pathEntries = path.split(QLatin1Char(':')); + for (int k = 0; k < pathEntries.size(); ++k) { + QByteArray tmp = QFile::encodeName(pathEntries.at(k)); + if (!tmp.endsWith('/')) tmp += '/'; + tmp += QFile::encodeName(program); + argv[0] = tmp.data(); + qt_safe_execv(argv[0], argv); + } + } + } else { + QByteArray tmp = QFile::encodeName(program); + argv[0] = tmp.data(); + qt_safe_execv(argv[0], argv); + } + + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + ::sigaction(SIGPIPE, &noaction, 0); + + // '\1' means execv failed + char c = '\1'; + qt_safe_write(startedPipe[1], &c, 1); + qt_safe_close(startedPipe[1]); + ::_exit(1); + } else if (doubleForkPid == -1) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + ::sigaction(SIGPIPE, &noaction, 0); + + // '\2' means internal error + char c = '\2'; + qt_safe_write(startedPipe[1], &c, 1); + } + + qt_safe_close(startedPipe[1]); + qt_safe_write(pidPipe[1], (const char *)&doubleForkPid, sizeof(pid_t)); + QT_CHDIR("/"); + ::_exit(1); + } + + qt_safe_close(startedPipe[1]); + qt_safe_close(pidPipe[1]); + + if (childPid == -1) { + qt_safe_close(startedPipe[0]); + qt_safe_close(pidPipe[0]); + return false; + } + + char reply = '\0'; + int startResult = qt_safe_read(startedPipe[0], &reply, 1); + int result; + qt_safe_close(startedPipe[0]); + qt_safe_waitpid(childPid, &result, 0); + bool success = (startResult != -1 && reply == '\0'); + if (success && pid) { + pid_t actualPid = 0; + if (qt_safe_read(pidPipe[0], (char *)&actualPid, sizeof(pid_t)) == sizeof(pid_t)) { + *pid = actualPid; + } else { + *pid = 0; + } + } + qt_safe_close(pidPipe[0]); + return success; +} + +void QProcessPrivate::initializeProcessManager() +{ + (void) processManager(); +} + +QT_END_NAMESPACE + +#include "qprocess_unix.moc" + +#endif // QT_NO_PROCESS diff --git a/src/corelib/io/qprocess_win.cpp b/src/corelib/io/qprocess_win.cpp new file mode 100644 index 0000000000..625ed9853f --- /dev/null +++ b/src/corelib/io/qprocess_win.cpp @@ -0,0 +1,855 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprocess.h" +#include "qprocess_p.h" +#include "qwindowspipewriter_p.h" + +#include <qdatetime.h> +#include <qdir.h> +#include <qfileinfo.h> +#include <qtimer.h> +#include <qthread.h> +#include <qmutex.h> +#include <qwaitcondition.h> +#include <private/qwineventnotifier_p.h> +#include <private/qthread_p.h> +#include <qdebug.h> + +#include "private/qfsfileengine_p.h" // for longFileName + + +#ifndef QT_NO_PROCESS + +QT_BEGIN_NAMESPACE + +//#define QPROCESS_DEBUG + +#define NOTIFYTIMEOUT 100 + +static void qt_create_pipe(Q_PIPE *pipe, bool in) +{ + // Open the pipes. Make non-inheritable copies of input write and output + // read handles to avoid non-closable handles (this is done by the + // DuplicateHandle() call). + +#if !defined(Q_OS_WINCE) + SECURITY_ATTRIBUTES secAtt = { sizeof( SECURITY_ATTRIBUTES ), NULL, TRUE }; + + HANDLE tmpHandle; + if (in) { // stdin + if (!CreatePipe(&pipe[0], &tmpHandle, &secAtt, 1024 * 1024)) + return; + if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, GetCurrentProcess(), + &pipe[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) + return; + } else { // stdout or stderr + if (!CreatePipe(&tmpHandle, &pipe[1], &secAtt, 1024 * 1024)) + return; + if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, GetCurrentProcess(), + &pipe[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) + return; + } + + CloseHandle(tmpHandle); +#else + Q_UNUSED(pipe); + Q_UNUSED(in); +#endif +} + +/* + Create the pipes to a QProcessPrivate::Channel. + + This function must be called in order: stdin, stdout, stderr +*/ +bool QProcessPrivate::createChannel(Channel &channel) +{ + Q_Q(QProcess); + + if (&channel == &stderrChannel && processChannelMode == QProcess::MergedChannels) { + return DuplicateHandle(GetCurrentProcess(), stdoutChannel.pipe[1], GetCurrentProcess(), + &stderrChannel.pipe[1], 0, TRUE, DUPLICATE_SAME_ACCESS); + } + + if (channel.type == Channel::Normal) { + // we're piping this channel to our own process + qt_create_pipe(channel.pipe, &channel == &stdinChannel); + + return true; + } else if (channel.type == Channel::Redirect) { + // we're redirecting the channel to/from a file + SECURITY_ATTRIBUTES secAtt = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; + + if (&channel == &stdinChannel) { + // try to open in read-only mode + channel.pipe[1] = INVALID_Q_PIPE; + channel.pipe[0] = + CreateFile((const wchar_t*)QFSFileEnginePrivate::longFileName(channel.file).utf16(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &secAtt, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (channel.pipe[0] != INVALID_Q_PIPE) + return true; + + q->setErrorString(QProcess::tr("Could not open input redirection for reading")); + } else { + // open in write mode + channel.pipe[0] = INVALID_Q_PIPE; + channel.pipe[1] = + CreateFile((const wchar_t *)QFSFileEnginePrivate::longFileName(channel.file).utf16(), + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &secAtt, + channel.append ? OPEN_ALWAYS : CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (channel.pipe[1] != INVALID_Q_PIPE) { + if (channel.append) { + SetFilePointer(channel.pipe[1], 0, NULL, FILE_END); + } + return true; + } + + q->setErrorString(QProcess::tr("Could not open output redirection for writing")); + } + + // could not open file + processError = QProcess::FailedToStart; + emit q->error(processError); + cleanup(); + return false; + } else { + Q_ASSERT_X(channel.process, "QProcess::start", "Internal error"); + + Channel *source; + Channel *sink; + + if (channel.type == Channel::PipeSource) { + // we are the source + source = &channel; + sink = &channel.process->stdinChannel; + + if (source->pipe[1] != INVALID_Q_PIPE) { + // already constructed by the sink + // make it inheritable + HANDLE tmpHandle = source->pipe[1]; + if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, + GetCurrentProcess(), &source->pipe[1], + 0, TRUE, DUPLICATE_SAME_ACCESS)) + return false; + + CloseHandle(tmpHandle); + return true; + } + + Q_ASSERT(source == &stdoutChannel); + Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink); + + qt_create_pipe(source->pipe, /* in = */ false); // source is stdout + sink->pipe[0] = source->pipe[0]; + source->pipe[0] = INVALID_Q_PIPE; + + return true; + } else { + // we are the sink; + source = &channel.process->stdoutChannel; + sink = &channel; + + if (sink->pipe[0] != INVALID_Q_PIPE) { + // already constructed by the source + // make it inheritable + HANDLE tmpHandle = sink->pipe[0]; + if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, + GetCurrentProcess(), &sink->pipe[0], + 0, TRUE, DUPLICATE_SAME_ACCESS)) + return false; + + CloseHandle(tmpHandle); + return true; + } + Q_ASSERT(sink == &stdinChannel); + Q_ASSERT(source->process == this && source->type == Channel::PipeSource); + + qt_create_pipe(sink->pipe, /* in = */ true); // sink is stdin + source->pipe[1] = sink->pipe[1]; + sink->pipe[1] = INVALID_Q_PIPE; + + return true; + } + } +} + +void QProcessPrivate::destroyPipe(Q_PIPE pipe[2]) +{ + if (pipe[0] == stdinChannel.pipe[0] && pipe[1] == stdinChannel.pipe[1] && pipeWriter) { + delete pipeWriter; + pipeWriter = 0; + } + + if (pipe[0] != INVALID_Q_PIPE) { + CloseHandle(pipe[0]); + pipe[0] = INVALID_Q_PIPE; + } + if (pipe[1] != INVALID_Q_PIPE) { + CloseHandle(pipe[1]); + pipe[1] = INVALID_Q_PIPE; + } +} + + +static QString qt_create_commandline(const QString &program, const QStringList &arguments) +{ + QString args; + if (!program.isEmpty()) { + QString programName = program; + if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' '))) + programName = QLatin1Char('\"') + programName + QLatin1Char('\"'); + programName.replace(QLatin1Char('/'), QLatin1Char('\\')); + + // add the prgram as the first arg ... it works better + args = programName + QLatin1Char(' '); + } + + for (int i=0; i<arguments.size(); ++i) { + QString tmp = arguments.at(i); + // in the case of \" already being in the string the \ must also be escaped + tmp.replace( QLatin1String("\\\""), QLatin1String("\\\\\"") ); + // escape a single " because the arguments will be parsed + tmp.replace( QLatin1Char('\"'), QLatin1String("\\\"") ); + if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) { + // The argument must not end with a \ since this would be interpreted + // as escaping the quote -- rather put the \ behind the quote: e.g. + // rather use "foo"\ than "foo\" + QString endQuote(QLatin1Char('\"')); + int i = tmp.length(); + while (i>0 && tmp.at(i-1) == QLatin1Char('\\')) { + --i; + endQuote += QLatin1Char('\\'); + } + args += QLatin1String(" \"") + tmp.left(i) + endQuote; + } else { + args += QLatin1Char(' ') + tmp; + } + } + return args; +} + +static QByteArray qt_create_environment(const QHash<QString, QString> &environment) +{ + QByteArray envlist; + if (!environment.isEmpty()) { + QHash<QString, QString> copy = environment; + + // add PATH if necessary (for DLL loading) + if (!copy.contains(QLatin1String("PATH"))) { + QByteArray path = qgetenv("PATH"); + if (!path.isEmpty()) + copy.insert(QLatin1String("PATH"), QString::fromLocal8Bit(path)); + } + + // add systemroot if needed + if (!copy.contains(QLatin1String("SYSTEMROOT"))) { + QByteArray systemRoot = qgetenv("SYSTEMROOT"); + if (!systemRoot.isEmpty()) + copy.insert(QLatin1String("SYSTEMROOT"), QString::fromLocal8Bit(systemRoot)); + } + + int pos = 0; + QHash<QString, QString>::ConstIterator it = copy.constBegin(), + end = copy.constEnd(); + + static const wchar_t equal = L'='; + static const wchar_t nul = L'\0'; + + for ( ; it != end; ++it) { + uint tmpSize = sizeof(wchar_t) * (it.key().length() + it.value().length() + 2); + // ignore empty strings + if (tmpSize == sizeof(wchar_t) * 2) + continue; + envlist.resize(envlist.size() + tmpSize); + + tmpSize = it.key().length() * sizeof(wchar_t); + memcpy(envlist.data()+pos, it.key().utf16(), tmpSize); + pos += tmpSize; + + memcpy(envlist.data()+pos, &equal, sizeof(wchar_t)); + pos += sizeof(wchar_t); + + tmpSize = it.value().length() * sizeof(wchar_t); + memcpy(envlist.data()+pos, it.value().utf16(), tmpSize); + pos += tmpSize; + + memcpy(envlist.data()+pos, &nul, sizeof(wchar_t)); + pos += sizeof(wchar_t); + } + // add the 2 terminating 0 (actually 4, just to be on the safe side) + envlist.resize( envlist.size()+4 ); + envlist[pos++] = 0; + envlist[pos++] = 0; + envlist[pos++] = 0; + envlist[pos++] = 0; + } + return envlist; +} + +void QProcessPrivate::startProcess() +{ + Q_Q(QProcess); + + bool success = false; + + if (pid) { + CloseHandle(pid->hThread); + CloseHandle(pid->hProcess); + delete pid; + pid = 0; + } + pid = new PROCESS_INFORMATION; + memset(pid, 0, sizeof(PROCESS_INFORMATION)); + + q->setProcessState(QProcess::Starting); + + if (!createChannel(stdinChannel) || + !createChannel(stdoutChannel) || + !createChannel(stderrChannel)) + return; + +#if defined(Q_OS_WINCE) + QString args = qt_create_commandline(QString(), arguments); +#else + QString args = qt_create_commandline(program, arguments); + QByteArray envlist; + if (environment.d.constData()) + envlist = qt_create_environment(environment.d.constData()->hash); +#endif + if (!nativeArguments.isEmpty()) { + if (!args.isEmpty()) + args += QLatin1Char(' '); + args += nativeArguments; + } + +#if defined QPROCESS_DEBUG + qDebug("Creating process"); + qDebug(" program : [%s]", program.toLatin1().constData()); + qDebug(" args : %s", args.toLatin1().constData()); + qDebug(" pass environment : %s", environment.isEmpty() ? "no" : "yes"); +#endif + +#if defined(Q_OS_WINCE) + QString fullPathProgram = program; + if (!QDir::isAbsolutePath(fullPathProgram)) + fullPathProgram = QFileInfo(fullPathProgram).absoluteFilePath(); + fullPathProgram.replace(QLatin1Char('/'), QLatin1Char('\\')); + success = CreateProcess((wchar_t*)fullPathProgram.utf16(), + (wchar_t*)args.utf16(), + 0, 0, false, 0, 0, 0, 0, pid); +#else + DWORD dwCreationFlags = CREATE_NO_WINDOW; + dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT; + STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + 0, 0, 0, + STARTF_USESTDHANDLES, + 0, 0, 0, + stdinChannel.pipe[0], stdoutChannel.pipe[1], stderrChannel.pipe[1] + }; + success = CreateProcess(0, (wchar_t*)args.utf16(), + 0, 0, TRUE, dwCreationFlags, + environment.isEmpty() ? 0 : envlist.data(), + workingDirectory.isEmpty() ? 0 : (wchar_t*)QDir::toNativeSeparators(workingDirectory).utf16(), + &startupInfo, pid); + if (!success) { + // Capture the error string before we do CloseHandle below + q->setErrorString(QProcess::tr("Process failed to start: %1").arg(qt_error_string())); + } + + if (stdinChannel.pipe[0] != INVALID_Q_PIPE) { + CloseHandle(stdinChannel.pipe[0]); + stdinChannel.pipe[0] = INVALID_Q_PIPE; + } + if (stdoutChannel.pipe[1] != INVALID_Q_PIPE) { + CloseHandle(stdoutChannel.pipe[1]); + stdoutChannel.pipe[1] = INVALID_Q_PIPE; + } + if (stderrChannel.pipe[1] != INVALID_Q_PIPE) { + CloseHandle(stderrChannel.pipe[1]); + stderrChannel.pipe[1] = INVALID_Q_PIPE; + } +#endif // Q_OS_WINCE + + if (!success) { + cleanup(); + processError = QProcess::FailedToStart; + emit q->error(processError); + q->setProcessState(QProcess::NotRunning); + return; + } + + q->setProcessState(QProcess::Running); + // User can call kill()/terminate() from the stateChanged() slot + // so check before proceeding + if (!pid) + return; + + if (threadData->eventDispatcher) { + processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q); + QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_processDied())); + processFinishedNotifier->setEnabled(true); + notifier = new QTimer(q); + QObject::connect(notifier, SIGNAL(timeout()), q, SLOT(_q_notified())); + notifier->start(NOTIFYTIMEOUT); + } + + // give the process a chance to start ... + Sleep(SLEEPMIN * 2); + _q_startupNotification(); +} + +bool QProcessPrivate::processStarted() +{ + return processState == QProcess::Running; +} + +qint64 QProcessPrivate::bytesAvailableFromStdout() const +{ + if (stdoutChannel.pipe[0] == INVALID_Q_PIPE) + return 0; + + DWORD bytesAvail = 0; +#if !defined(Q_OS_WINCE) + PeekNamedPipe(stdoutChannel.pipe[0], 0, 0, 0, &bytesAvail, 0); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::bytesAvailableFromStdout() == %d", bytesAvail); +#endif + if (processChannelMode == QProcess::ForwardedChannels && bytesAvail > 0) { + QByteArray buf(bytesAvail, 0); + DWORD bytesRead = 0; + if (ReadFile(stdoutChannel.pipe[0], buf.data(), buf.size(), &bytesRead, 0) && bytesRead > 0) { + HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + if (hStdout) { + DWORD bytesWritten = 0; + WriteFile(hStdout, buf.data(), bytesRead, &bytesWritten, 0); + } + } + bytesAvail = 0; + } +#endif + return bytesAvail; +} + +qint64 QProcessPrivate::bytesAvailableFromStderr() const +{ + if (stderrChannel.pipe[0] == INVALID_Q_PIPE) + return 0; + + DWORD bytesAvail = 0; +#if !defined(Q_OS_WINCE) + PeekNamedPipe(stderrChannel.pipe[0], 0, 0, 0, &bytesAvail, 0); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::bytesAvailableFromStderr() == %d", bytesAvail); +#endif + if (processChannelMode == QProcess::ForwardedChannels && bytesAvail > 0) { + QByteArray buf(bytesAvail, 0); + DWORD bytesRead = 0; + if (ReadFile(stderrChannel.pipe[0], buf.data(), buf.size(), &bytesRead, 0) && bytesRead > 0) { + HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE); + if (hStderr) { + DWORD bytesWritten = 0; + WriteFile(hStderr, buf.data(), bytesRead, &bytesWritten, 0); + } + } + bytesAvail = 0; + } +#endif + return bytesAvail; +} + +qint64 QProcessPrivate::readFromStdout(char *data, qint64 maxlen) +{ + DWORD read = qMin(maxlen, bytesAvailableFromStdout()); + DWORD bytesRead = 0; + + if (read > 0 && !ReadFile(stdoutChannel.pipe[0], data, read, &bytesRead, 0)) + return -1; + return bytesRead; +} + +qint64 QProcessPrivate::readFromStderr(char *data, qint64 maxlen) +{ + DWORD read = qMin(maxlen, bytesAvailableFromStderr()); + DWORD bytesRead = 0; + + if (read > 0 && !ReadFile(stderrChannel.pipe[0], data, read, &bytesRead, 0)) + return -1; + return bytesRead; +} + + +static BOOL QT_WIN_CALLBACK qt_terminateApp(HWND hwnd, LPARAM procId) +{ + DWORD currentProcId = 0; + GetWindowThreadProcessId(hwnd, ¤tProcId); + if (currentProcId == (DWORD)procId) + PostMessage(hwnd, WM_CLOSE, 0, 0); + + return TRUE; +} + +void QProcessPrivate::terminateProcess() +{ + if (pid) { + EnumWindows(qt_terminateApp, (LPARAM)pid->dwProcessId); + PostThreadMessage(pid->dwThreadId, WM_CLOSE, 0, 0); + } +} + +void QProcessPrivate::killProcess() +{ + if (pid) + TerminateProcess(pid->hProcess, 0xf291); +} + +bool QProcessPrivate::waitForStarted(int) +{ + Q_Q(QProcess); + + if (processStarted()) + return true; + + if (processError == QProcess::FailedToStart) + return false; + + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; +} + +bool QProcessPrivate::waitForReadyRead(int msecs) +{ + Q_Q(QProcess); + +#if defined(Q_OS_WINCE) + processError = QProcess::ReadError; + q->setErrorString(QProcess::tr("Error reading from process")); + emit q->error(processError); + return false; +#endif + + QIncrementalSleepTimer timer(msecs); + + forever { + if (!writeBuffer.isEmpty() && !_q_canWrite()) + return false; + if (pipeWriter && pipeWriter->waitForWrite(0)) + timer.resetIncrements(); + bool readyReadEmitted = false; + if (bytesAvailableFromStdout() != 0) { + readyReadEmitted = _q_canReadStandardOutput() ? true : readyReadEmitted; + timer.resetIncrements(); + } + + if (bytesAvailableFromStderr() != 0) { + readyReadEmitted = _q_canReadStandardError() ? true : readyReadEmitted; + timer.resetIncrements(); + } + + if (readyReadEmitted) + return true; + + if (!pid) + return false; + if (WaitForSingleObject(pid->hProcess, 0) == WAIT_OBJECT_0) { + // find the return value if there is noew data to read + _q_processDied(); + return false; + } + + Sleep(timer.nextSleepTime()); + if (timer.hasTimedOut()) + break; + } + + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; +} + +bool QProcessPrivate::waitForBytesWritten(int msecs) +{ + Q_Q(QProcess); + +#if defined(Q_OS_WINCE) + processError = QProcess::ReadError; + q->setErrorString(QProcess::tr("Error reading from process")); + emit q->error(processError); + return false; +#endif + + QIncrementalSleepTimer timer(msecs); + + forever { + // Check if we have any data pending: the pipe writer has + // bytes waiting to written, or it has written data since the + // last time we called pipeWriter->waitForWrite(). + bool pendingDataInPipe = pipeWriter && (pipeWriter->bytesToWrite() || pipeWriter->hadWritten()); + + // If we don't have pending data, and our write buffer is + // empty, we fail. + if (!pendingDataInPipe && writeBuffer.isEmpty()) + return false; + + // If we don't have pending data and we do have data in our + // write buffer, try to flush that data over to the pipe + // writer. Fail on error. + if (!pendingDataInPipe) { + if (!_q_canWrite()) + return false; + } + + // Wait for the pipe writer to acknowledge that it has + // written. This will succeed if either the pipe writer has + // already written the data, or if it manages to write data + // within the given timeout. If the write buffer was non-empty + // and the pipeWriter is now dead, that means _q_canWrite() + // destroyed the writer after it successfully wrote the last + // batch. + if (!pipeWriter || pipeWriter->waitForWrite(0)) + return true; + + // If we wouldn't write anything, check if we can read stdout. + if (bytesAvailableFromStdout() != 0) { + _q_canReadStandardOutput(); + timer.resetIncrements(); + } + + // Check if we can read stderr. + if (bytesAvailableFromStderr() != 0) { + _q_canReadStandardError(); + timer.resetIncrements(); + } + + // Check if the process died while reading. + if (!pid) + return false; + + // Wait for the process to signal any change in its state, + // such as incoming data, or if the process died. + if (WaitForSingleObject(pid->hProcess, 0) == WAIT_OBJECT_0) { + _q_processDied(); + return false; + } + + // Only wait for as long as we've been asked. + if (timer.hasTimedOut()) + break; + } + + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; +} + + +bool QProcessPrivate::waitForFinished(int msecs) +{ + Q_Q(QProcess); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::waitForFinished(%d)", msecs); +#endif + + QIncrementalSleepTimer timer(msecs); + + forever { + if (!writeBuffer.isEmpty() && !_q_canWrite()) + return false; + if (pipeWriter && pipeWriter->waitForWrite(0)) + timer.resetIncrements(); + + if (bytesAvailableFromStdout() != 0) { + _q_canReadStandardOutput(); + timer.resetIncrements(); + } + + if (bytesAvailableFromStderr() != 0) { + _q_canReadStandardError(); + timer.resetIncrements(); + } + + if (!pid) + return true; + + if (WaitForSingleObject(pid->hProcess, timer.nextSleepTime()) == WAIT_OBJECT_0) { + _q_processDied(); + return true; + } + + if (timer.hasTimedOut()) + break; + } + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; +} + + +void QProcessPrivate::findExitCode() +{ + DWORD theExitCode; + if (GetExitCodeProcess(pid->hProcess, &theExitCode)) { + exitCode = theExitCode; + //### for now we assume a crash if exit code is less than -1 or the magic number + crashed = (exitCode == 0xf291 || (int)exitCode < 0); + } +} + +void QProcessPrivate::flushPipeWriter() +{ + if (pipeWriter && pipeWriter->bytesToWrite() > 0) { + pipeWriter->waitForWrite(ULONG_MAX); + } +} + +qint64 QProcessPrivate::pipeWriterBytesToWrite() const +{ + return pipeWriter ? pipeWriter->bytesToWrite() : qint64(0); +} + +qint64 QProcessPrivate::writeToStdin(const char *data, qint64 maxlen) +{ + Q_Q(QProcess); + +#if defined(Q_OS_WINCE) + processError = QProcess::WriteError; + q->setErrorString(QProcess::tr("Error writing to process")); + emit q->error(processError); + return -1; +#endif + + if (!pipeWriter) { + pipeWriter = new QWindowsPipeWriter(stdinChannel.pipe[1], q); + pipeWriter->start(); + } + + return pipeWriter->write(data, maxlen); +} + +bool QProcessPrivate::waitForWrite(int msecs) +{ + Q_Q(QProcess); + + if (!pipeWriter || pipeWriter->waitForWrite(msecs)) + return true; + + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; +} + +void QProcessPrivate::_q_notified() +{ + notifier->stop(); + + if (!writeBuffer.isEmpty() && (!pipeWriter || pipeWriter->waitForWrite(0))) + _q_canWrite(); + + if (bytesAvailableFromStdout()) + _q_canReadStandardOutput(); + + if (bytesAvailableFromStderr()) + _q_canReadStandardError(); + + if (processState != QProcess::NotRunning) + notifier->start(NOTIFYTIMEOUT); +} + +bool QProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDir, qint64 *pid) +{ +#if defined(Q_OS_WINCE) + Q_UNUSED(workingDir); + QString args = qt_create_commandline(QString(), arguments); +#else + QString args = qt_create_commandline(program, arguments); +#endif + + bool success = false; + + PROCESS_INFORMATION pinfo; + +#if defined(Q_OS_WINCE) + QString fullPathProgram = program; + if (!QDir::isAbsolutePath(fullPathProgram)) + fullPathProgram.prepend(QDir::currentPath().append(QLatin1Char('/'))); + fullPathProgram.replace(QLatin1Char('/'), QLatin1Char('\\')); + success = CreateProcess((wchar_t*)fullPathProgram.utf16(), + (wchar_t*)args.utf16(), + 0, 0, false, CREATE_NEW_CONSOLE, 0, 0, 0, &pinfo); +#else + STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + success = CreateProcess(0, (wchar_t*)args.utf16(), + 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, 0, + workingDir.isEmpty() ? 0 : (wchar_t*)workingDir.utf16(), + &startupInfo, &pinfo); +#endif // Q_OS_WINCE + + if (success) { + CloseHandle(pinfo.hThread); + CloseHandle(pinfo.hProcess); + if (pid) + *pid = pinfo.dwProcessId; + } + + return success; +} + +QT_END_NAMESPACE + +#endif // QT_NO_PROCESS diff --git a/src/corelib/io/qresource.cpp b/src/corelib/io/qresource.cpp new file mode 100644 index 0000000000..cd8c1a3076 --- /dev/null +++ b/src/corelib/io/qresource.cpp @@ -0,0 +1,1496 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qresource.h" +#include "qresource_p.h" +#include "qresource_iterator_p.h" +#include "qset.h" +#include "qhash.h" +#include "qmutex.h" +#include "qdebug.h" +#include "qlocale.h" +#include "qglobal.h" +#include "qvector.h" +#include "qdatetime.h" +#include "qbytearray.h" +#include "qstringlist.h" +#include <qshareddata.h> +#include <qplatformdefs.h> +#include "private/qabstractfileengine_p.h" + +#ifdef Q_OS_UNIX +# include "private/qcore_unix_p.h" +#endif + +//#define DEBUG_RESOURCE_MATCH + +#if defined(Q_OS_VXWORKS) +# if defined(m_data) +# undef m_data +# endif +# if defined(m_len) +# undef m_len +# endif +#endif + +QT_BEGIN_NAMESPACE + + +class QStringSplitter +{ +public: + QStringSplitter(const QString &s) + : m_string(s), m_data(m_string.constData()), m_len(s.length()), m_pos(0) + { + m_splitChar = QLatin1Char('/'); + } + + inline bool hasNext() { + while (m_pos < m_len && m_data[m_pos] == m_splitChar) + ++m_pos; + return m_pos < m_len; + } + + inline QStringRef next() { + int start = m_pos; + while (m_pos < m_len && m_data[m_pos] != m_splitChar) + ++m_pos; + return QStringRef(&m_string, start, m_pos - start); + } + + QString m_string; + const QChar *m_data; + QChar m_splitChar; + int m_len; + int m_pos; +}; + + +//resource glue +class QResourceRoot +{ + enum Flags + { + Compressed = 0x01, + Directory = 0x02 + }; + const uchar *tree, *names, *payloads; + inline int findOffset(int node) const { return node * 14; } //sizeof each tree element + int hash(int node) const; + QString name(int node) const; + short flags(int node) const; +public: + mutable QAtomicInt ref; + + inline QResourceRoot(): tree(0), names(0), payloads(0) {} + inline QResourceRoot(const uchar *t, const uchar *n, const uchar *d) { setSource(t, n, d); } + virtual ~QResourceRoot() { } + int findNode(const QString &path, const QLocale &locale=QLocale()) const; + inline bool isContainer(int node) const { return flags(node) & Directory; } + inline bool isCompressed(int node) const { return flags(node) & Compressed; } + const uchar *data(int node, qint64 *size) const; + QStringList children(int node) const; + virtual QString mappingRoot() const { return QString(); } + bool mappingRootSubdir(const QString &path, QString *match=0) const; + inline bool operator==(const QResourceRoot &other) const + { return tree == other.tree && names == other.names && payloads == other.payloads; } + inline bool operator!=(const QResourceRoot &other) const + { return !operator==(other); } + enum ResourceRootType { Resource_Builtin, Resource_File, Resource_Buffer }; + virtual ResourceRootType type() const { return Resource_Builtin; } + +protected: + inline void setSource(const uchar *t, const uchar *n, const uchar *d) { + tree = t; + names = n; + payloads = d; + } +}; + +static QString cleanPath(const QString &_path) +{ + QString path = QDir::cleanPath(_path); + // QDir::cleanPath does not remove two trailing slashes under _Windows_ + // due to support for UNC paths. Remove those manually. + if (path.startsWith(QLatin1String("//"))) + path.remove(0, 1); + return path; +} + +Q_DECLARE_TYPEINFO(QResourceRoot, Q_MOVABLE_TYPE); + +Q_GLOBAL_STATIC_WITH_ARGS(QMutex, resourceMutex, (QMutex::Recursive)) + +typedef QList<QResourceRoot*> ResourceList; +Q_GLOBAL_STATIC(ResourceList, resourceList) + +Q_GLOBAL_STATIC(QStringList, resourceSearchPaths) + +/*! + \class QResource + \brief The QResource class provides an interface for reading directly from resources. + + \ingroup io + + \reentrant + \since 4.2 + + QResource is an object that represents a set of data (and possibly + children) relating to a single resource entity. QResource gives direct + access to the bytes in their raw format. In this way direct access + allows reading data without buffer copying or indirection. Indirection + is often useful when interacting with the resource entity as if it is a + file, this can be achieved with QFile. The data and children behind a + QResource are normally compiled into an application/library, but it is + also possible to load a resource at runtime. When loaded at run time + the resource file will be loaded as one big set of data and then given + out in pieces via references into the resource tree. + + A QResource can either be loaded with an absolute path, either treated + as a file system rooted with a \c{/} character, or in resource notation + rooted with a \c{:} character. A relative resource can also be opened + which will be found in the list of paths returned by QDir::searchPaths(). + + A QResource that is representing a file will have data backing it, this + data can possibly be compressed, in which case qUncompress() must be + used to access the real data; this happens implicitly when accessed + through a QFile. A QResource that is representing a directory will have + only children and no data. + + \section1 Dynamic Resource Loading + + A resource can be left out of an application's binary and loaded when + it is needed at run-time by using the registerResource() function. The + resource file passed into registerResource() must be a binary resource + as created by rcc. Further information about binary resources can be + found in \l{The Qt Resource System} documentation. + + This can often be useful when loading a large set of application icons + that may change based on a setting, or that can be edited by a user and + later recreated. The resource is immediately loaded into memory, either + as a result of a single file read operation, or as a memory mapped file. + + This approach can prove to be a significant performance gain as only a + single file will be loaded, and pieces of data will be given out via the + path requested in setFileName(). + + The unregisterResource() function removes a reference to a particular + file. If there are QResources that currently reference resources related + to the unregistered file, they will continue to be valid but the resource + file itself will be removed from the resource roots, and thus no further + QResource can be created pointing into this resource data. The resource + itself will be unmapped from memory when the last QResource that points + to it is destroyed. + + \sa {The Qt Resource System}, QFile, QDir, QFileInfo +*/ + +class QResourcePrivate { +public: + inline QResourcePrivate(QResource *_q) : q_ptr(_q) { clear(); } + inline ~QResourcePrivate() { clear(); } + + void ensureInitialized() const; + void ensureChildren() const; + + bool load(const QString &file); + void clear(); + + QLocale locale; + QString fileName, absoluteFilePath; + QList<QResourceRoot*> related; + uint container : 1; + mutable uint compressed : 1; + mutable qint64 size; + mutable const uchar *data; + mutable QStringList children; + + QResource *q_ptr; + Q_DECLARE_PUBLIC(QResource) +}; + +void +QResourcePrivate::clear() +{ + absoluteFilePath.clear(); + compressed = 0; + data = 0; + size = 0; + children.clear(); + container = 0; + for(int i = 0; i < related.size(); ++i) { + QResourceRoot *root = related.at(i); + if(!root->ref.deref()) + delete root; + } + related.clear(); +} + +bool +QResourcePrivate::load(const QString &file) +{ + related.clear(); + QMutexLocker lock(resourceMutex()); + const ResourceList *list = resourceList(); + QString cleaned = cleanPath(file); + for(int i = 0; i < list->size(); ++i) { + QResourceRoot *res = list->at(i); + const int node = res->findNode(cleaned, locale); + if(node != -1) { + if(related.isEmpty()) { + container = res->isContainer(node); + if(!container) { + data = res->data(node, &size); + compressed = res->isCompressed(node); + } else { + data = 0; + size = 0; + compressed = 0; + } + } else if(res->isContainer(node) != container) { + qWarning("QResourceInfo: Resource [%s] has both data and children!", file.toLatin1().constData()); + } + res->ref.ref(); + related.append(res); + } else if(res->mappingRootSubdir(file)) { + container = true; + data = 0; + size = 0; + compressed = 0; + res->ref.ref(); + related.append(res); + } + } + return !related.isEmpty(); +} + +void +QResourcePrivate::ensureInitialized() const +{ + if(!related.isEmpty()) + return; + QResourcePrivate *that = const_cast<QResourcePrivate *>(this); + if(fileName == QLatin1String(":")) + that->fileName += QLatin1Char('/'); + that->absoluteFilePath = fileName; + if(!that->absoluteFilePath.startsWith(QLatin1Char(':'))) + that->absoluteFilePath.prepend(QLatin1Char(':')); + + QString path = fileName; + if(path.startsWith(QLatin1Char(':'))) + path = path.mid(1); + + if(path.startsWith(QLatin1Char('/'))) { + that->load(path); + } else { + QMutexLocker lock(resourceMutex()); + QStringList searchPaths = *resourceSearchPaths(); + searchPaths << QLatin1String(""); + for(int i = 0; i < searchPaths.size(); ++i) { + const QString searchPath(searchPaths.at(i) + QLatin1Char('/') + path); + if(that->load(searchPath)) { + that->absoluteFilePath = QLatin1Char(':') + searchPath; + break; + } + } + } +} + +void +QResourcePrivate::ensureChildren() const +{ + ensureInitialized(); + if(!children.isEmpty() || !container || related.isEmpty()) + return; + + QString path = absoluteFilePath, k; + if(path.startsWith(QLatin1Char(':'))) + path = path.mid(1); + QSet<QString> kids; + QString cleaned = cleanPath(path); + for(int i = 0; i < related.size(); ++i) { + QResourceRoot *res = related.at(i); + if(res->mappingRootSubdir(path, &k) && !k.isEmpty()) { + if(!kids.contains(k)) { + children += k; + kids.insert(k); + } + } else { + const int node = res->findNode(cleaned); + if(node != -1) { + QStringList related_children = res->children(node); + for(int kid = 0; kid < related_children.size(); ++kid) { + k = related_children.at(kid); + if(!kids.contains(k)) { + children += k; + kids.insert(k); + } + } + } + } + } +} + +/*! + Constructs a QResource pointing to \a file. \a locale is used to + load a specific localization of a resource data. + + \sa QFileInfo, QDir::searchPaths(), setFileName(), setLocale() +*/ + +QResource::QResource(const QString &file, const QLocale &locale) : d_ptr(new QResourcePrivate(this)) +{ + Q_D(QResource); + d->fileName = file; + d->locale = locale; +} + +/*! + Releases the resources of the QResource object. +*/ +QResource::~QResource() +{ +} + +/*! + Sets a QResource to only load the localization of resource to for \a + locale. If a resource for the specific locale is not found then the + C locale is used. + + \sa setFileName() +*/ + +void QResource::setLocale(const QLocale &locale) +{ + Q_D(QResource); + d->clear(); + d->locale = locale; +} + +/*! + Returns the locale used to locate the data for the QResource. +*/ + +QLocale QResource::locale() const +{ + Q_D(const QResource); + return d->locale; +} + +/*! + Sets a QResource to point to \a file. \a file can either be absolute, + in which case it is opened directly, if relative then the file will be + tried to be found in QDir::searchPaths(). + + \sa absoluteFilePath() +*/ + +void QResource::setFileName(const QString &file) +{ + Q_D(QResource); + d->clear(); + d->fileName = file; +} + +/*! + Returns the full path to the file that this QResource represents as it + was passed. + + \sa absoluteFilePath() +*/ + +QString QResource::fileName() const +{ + Q_D(const QResource); + d->ensureInitialized(); + return d->fileName; +} + +/*! + Returns the real path that this QResource represents, if the resource + was found via the QDir::searchPaths() it will be indicated in the path. + + \sa fileName() +*/ + +QString QResource::absoluteFilePath() const +{ + Q_D(const QResource); + d->ensureInitialized(); + return d->absoluteFilePath; +} + +/*! + Returns true if the resource really exists in the resource hierarchy, + false otherwise. + +*/ + +bool QResource::isValid() const +{ + Q_D(const QResource); + d->ensureInitialized(); + return !d->related.isEmpty(); +} + +/*! + \fn bool QResource::isFile() const + + Returns true if the resource represents a file and thus has data + backing it, false if it represents a directory. + + \sa isDir() +*/ + + +/*! + Returns true if the resource represents a file and the data backing it + is in a compressed format, false otherwise. + + \sa data(), isFile() +*/ + +bool QResource::isCompressed() const +{ + Q_D(const QResource); + d->ensureInitialized(); + return d->compressed; +} + +/*! + Returns the size of the data backing the resource. + + \sa data(), isFile() +*/ + +qint64 QResource::size() const +{ + Q_D(const QResource); + d->ensureInitialized(); + return d->size; +} + +/*! + Returns direct access to a read only segment of data that this resource + represents. If the resource is compressed the data returns is + compressed and qUncompress() must be used to access the data. If the + resource is a directory 0 is returned. + + \sa size(), isCompressed(), isFile() +*/ + +const uchar *QResource::data() const +{ + Q_D(const QResource); + d->ensureInitialized(); + return d->data; +} + +/*! + Returns true if the resource represents a directory and thus may have + children() in it, false if it represents a file. + + \sa isFile() +*/ + +bool QResource::isDir() const +{ + Q_D(const QResource); + d->ensureInitialized(); + return d->container; +} + +/*! + Returns a list of all resources in this directory, if the resource + represents a file the list will be empty. + + \sa isDir() +*/ + +QStringList QResource::children() const +{ + Q_D(const QResource); + d->ensureChildren(); + return d->children; +} + +/*! + \obsolete + + Use QDir::addSearchPath() with a prefix instead. + + Adds \a path to the search paths searched in to find resources that are + not specified with an absolute path. The \a path must be an absolute + path (start with \c{/}). + + The default search path is to search only in the root (\c{:/}). The last + path added will be consulted first upon next QResource creation. +*/ +void +QResource::addSearchPath(const QString &path) +{ + if (!path.startsWith(QLatin1Char('/'))) { + qWarning("QResource::addResourceSearchPath: Search paths must be absolute (start with /) [%s]", + path.toLocal8Bit().data()); + return; + } + QMutexLocker lock(resourceMutex()); + resourceSearchPaths()->prepend(path); +} + +/*! + \obsolete + + Use QDir::searchPaths() instead. + + Returns the current search path list. This list is consulted when + creating a relative resource. + + \sa QDir::addSearchPath() QDir::setSearchPaths() +*/ + +QStringList +QResource::searchPaths() +{ + QMutexLocker lock(resourceMutex()); + return *resourceSearchPaths(); +} + +inline int QResourceRoot::hash(int node) const +{ + if(!node) //root + return 0; + const int offset = findOffset(node); + int name_offset = (tree[offset+0] << 24) + (tree[offset+1] << 16) + + (tree[offset+2] << 8) + (tree[offset+3] << 0); + name_offset += 2; //jump past name length + return (names[name_offset+0] << 24) + (names[name_offset+1] << 16) + + (names[name_offset+2] << 8) + (names[name_offset+3] << 0); +} +inline QString QResourceRoot::name(int node) const +{ + if(!node) // root + return QString(); + const int offset = findOffset(node); + + QString ret; + int name_offset = (tree[offset+0] << 24) + (tree[offset+1] << 16) + + (tree[offset+2] << 8) + (tree[offset+3] << 0); + const short name_length = (names[name_offset+0] << 8) + + (names[name_offset+1] << 0); + name_offset += 2; + name_offset += 4; //jump past hash + + ret.resize(name_length); + QChar *strData = ret.data(); + for(int i = 0; i < name_length*2; i+=2) { + QChar c(names[name_offset+i+1], names[name_offset+i]); + *strData = c; + ++strData; + } + return ret; +} + +int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const +{ + QString path = _path; + { + QString root = mappingRoot(); + if(!root.isEmpty()) { + if(root == path) { + path = QLatin1Char('/'); + } else { + if(!root.endsWith(QLatin1Char('/'))) + root += QLatin1Char('/'); + if(path.size() >= root.size() && path.startsWith(root)) + path = path.mid(root.length()-1); + if(path.isEmpty()) + path = QLatin1Char('/'); + } + } + } +#ifdef DEBUG_RESOURCE_MATCH + qDebug() << "!!!!" << "START" << path << locale.country() << locale.language(); +#endif + + if(path == QLatin1String("/")) + return 0; + + //the root node is always first + int child_count = (tree[6] << 24) + (tree[7] << 16) + + (tree[8] << 8) + (tree[9] << 0); + int child = (tree[10] << 24) + (tree[11] << 16) + + (tree[12] << 8) + (tree[13] << 0); + + //now iterate up the tree + int node = -1; + + QStringSplitter splitter(path); + while (child_count && splitter.hasNext()) { + QStringRef segment = splitter.next(); + +#ifdef DEBUG_RESOURCE_MATCH + qDebug() << " CHILDREN" << segment; + for(int j = 0; j < child_count; ++j) { + qDebug() << " " << child+j << " :: " << name(child+j); + } +#endif + const int h = qHash(segment); + + //do the binary search for the hash + int l = 0, r = child_count-1; + int sub_node = (l+r+1)/2; + while(r != l) { + const int sub_node_hash = hash(child+sub_node); + if(h == sub_node_hash) + break; + else if(h < sub_node_hash) + r = sub_node - 1; + else + l = sub_node; + sub_node = (l + r + 1) / 2; + } + sub_node += child; + + //now do the "harder" compares + bool found = false; + if(hash(sub_node) == h) { + while(sub_node > child && hash(sub_node-1) == h) //backup for collisions + --sub_node; + for(; sub_node < child+child_count && hash(sub_node) == h; ++sub_node) { //here we go... + if(name(sub_node) == segment) { + found = true; + int offset = findOffset(sub_node); +#ifdef DEBUG_RESOURCE_MATCH + qDebug() << " TRY" << sub_node << name(sub_node) << offset; +#endif + offset += 4; //jump past name + + const short flags = (tree[offset+0] << 8) + + (tree[offset+1] << 0); + offset += 2; + + if(!splitter.hasNext()) { + if(!(flags & Directory)) { + const short country = (tree[offset+0] << 8) + + (tree[offset+1] << 0); + offset += 2; + + const short language = (tree[offset+0] << 8) + + (tree[offset+1] << 0); + offset += 2; +#ifdef DEBUG_RESOURCE_MATCH + qDebug() << " " << "LOCALE" << country << language; +#endif + if(country == locale.country() && language == locale.language()) { +#ifdef DEBUG_RESOURCE_MATCH + qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node; +#endif + return sub_node; + } else if((country == QLocale::AnyCountry && language == locale.language()) || + (country == QLocale::AnyCountry && language == QLocale::C && node == -1)) { + node = sub_node; + } + continue; + } else { +#ifdef DEBUG_RESOURCE_MATCH + qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node; +#endif + + return sub_node; + } + } + + if(!(flags & Directory)) + return -1; + + child_count = (tree[offset+0] << 24) + (tree[offset+1] << 16) + + (tree[offset+2] << 8) + (tree[offset+3] << 0); + offset += 4; + child = (tree[offset+0] << 24) + (tree[offset+1] << 16) + + (tree[offset+2] << 8) + (tree[offset+3] << 0); + break; + } + } + } + if(!found) + break; + } +#ifdef DEBUG_RESOURCE_MATCH + qDebug() << "!!!!" << "FINISHED" << __LINE__ << node; +#endif + return node; +} +short QResourceRoot::flags(int node) const +{ + if(node == -1) + return 0; + const int offset = findOffset(node) + 4; //jump past name + return (tree[offset+0] << 8) + (tree[offset+1] << 0); +} +const uchar *QResourceRoot::data(int node, qint64 *size) const +{ + if(node == -1) { + *size = 0; + return 0; + } + int offset = findOffset(node) + 4; //jump past name + + const short flags = (tree[offset+0] << 8) + (tree[offset+1] << 0); + offset += 2; + + offset += 4; //jump past locale + + if(!(flags & Directory)) { + const int data_offset = (tree[offset+0] << 24) + (tree[offset+1] << 16) + + (tree[offset+2] << 8) + (tree[offset+3] << 0); + const uint data_length = (payloads[data_offset+0] << 24) + (payloads[data_offset+1] << 16) + + (payloads[data_offset+2] << 8) + (payloads[data_offset+3] << 0); + const uchar *ret = payloads+data_offset+4; + *size = data_length; + return ret; + } + *size = 0; + return 0; +} +QStringList QResourceRoot::children(int node) const +{ + if(node == -1) + return QStringList(); + int offset = findOffset(node) + 4; //jump past name + + const short flags = (tree[offset+0] << 8) + (tree[offset+1] << 0); + offset += 2; + + QStringList ret; + if(flags & Directory) { + const int child_count = (tree[offset+0] << 24) + (tree[offset+1] << 16) + + (tree[offset+2] << 8) + (tree[offset+3] << 0); + offset += 4; + const int child_off = (tree[offset+0] << 24) + (tree[offset+1] << 16) + + (tree[offset+2] << 8) + (tree[offset+3] << 0); + for(int i = child_off; i < child_off+child_count; ++i) + ret << name(i); + } + return ret; +} +bool QResourceRoot::mappingRootSubdir(const QString &path, QString *match) const +{ + const QString root = mappingRoot(); + if(!root.isEmpty()) { + const QStringList root_segments = root.split(QLatin1Char('/'), QString::SkipEmptyParts), + path_segments = path.split(QLatin1Char('/'), QString::SkipEmptyParts); + if(path_segments.size() <= root_segments.size()) { + int matched = 0; + for(int i = 0; i < path_segments.size(); ++i) { + if(root_segments[i] != path_segments[i]) + break; + ++matched; + } + if(matched == path_segments.size()) { + if(match && root_segments.size() > matched) + *match = root_segments.at(matched); + return true; + } + } + } + return false; +} + +Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree, + const unsigned char *name, const unsigned char *data) +{ + QMutexLocker lock(resourceMutex()); + if(version == 0x01 && resourceList()) { + bool found = false; + QResourceRoot res(tree, name, data); + for(int i = 0; i < resourceList()->size(); ++i) { + if(*resourceList()->at(i) == res) { + found = true; + break; + } + } + if(!found) { + QResourceRoot *root = new QResourceRoot(tree, name, data); + root->ref.ref(); + resourceList()->append(root); + } + return true; + } + return false; +} + +Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tree, + const unsigned char *name, const unsigned char *data) +{ + QMutexLocker lock(resourceMutex()); + if(version == 0x01 && resourceList()) { + QResourceRoot res(tree, name, data); + for(int i = 0; i < resourceList()->size(); ) { + if(*resourceList()->at(i) == res) { + QResourceRoot *root = resourceList()->takeAt(i); + if(!root->ref.deref()) + delete root; + } else { + ++i; + } + } + return true; + } + return false; +} + +//run time resource creation + +class QDynamicBufferResourceRoot: public QResourceRoot +{ + QString root; + const uchar *buffer; + +public: + inline QDynamicBufferResourceRoot(const QString &_root) : root(_root), buffer(0) { } + inline ~QDynamicBufferResourceRoot() { } + inline const uchar *mappingBuffer() const { return buffer; } + virtual QString mappingRoot() const { return root; } + virtual ResourceRootType type() const { return Resource_Buffer; } + + bool registerSelf(const uchar *b) { + //setup the data now + int offset = 0; + + //magic number + if(b[offset+0] != 'q' || b[offset+1] != 'r' || + b[offset+2] != 'e' || b[offset+3] != 's') { + return false; + } + offset += 4; + + const int version = (b[offset+0] << 24) + (b[offset+1] << 16) + + (b[offset+2] << 8) + (b[offset+3] << 0); + offset += 4; + + const int tree_offset = (b[offset+0] << 24) + (b[offset+1] << 16) + + (b[offset+2] << 8) + (b[offset+3] << 0); + offset += 4; + + const int data_offset = (b[offset+0] << 24) + (b[offset+1] << 16) + + (b[offset+2] << 8) + (b[offset+3] << 0); + offset += 4; + + const int name_offset = (b[offset+0] << 24) + (b[offset+1] << 16) + + (b[offset+2] << 8) + (b[offset+3] << 0); + offset += 4; + + if(version == 0x01) { + buffer = b; + setSource(b+tree_offset, b+name_offset, b+data_offset); + return true; + } + return false; + } +}; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN) && !defined (Q_OS_NACL) && !defined(Q_OS_INTEGRITY) +#define QT_USE_MMAP +#endif + +// most of the headers below are already included in qplatformdefs.h +// also this lacks Large File support but that's probably irrelevant +#if defined(QT_USE_MMAP) +// for mmap +QT_BEGIN_INCLUDE_NAMESPACE +#include <sys/mman.h> +#include <errno.h> +QT_END_INCLUDE_NAMESPACE +#endif + + + +class QDynamicFileResourceRoot: public QDynamicBufferResourceRoot +{ + QString fileName; + // for mmap'ed files, this is what needs to be unmapped. + uchar *unmapPointer; + unsigned int unmapLength; + +public: + inline QDynamicFileResourceRoot(const QString &_root) : QDynamicBufferResourceRoot(_root), unmapPointer(0), unmapLength(0) { } + ~QDynamicFileResourceRoot() { +#if defined(QT_USE_MMAP) + if (unmapPointer) { + munmap((char*)unmapPointer, unmapLength); + unmapPointer = 0; + unmapLength = 0; + } else +#endif + { + delete [] (uchar *)mappingBuffer(); + } + } + QString mappingFile() const { return fileName; } + virtual ResourceRootType type() const { return Resource_File; } + + bool registerSelf(const QString &f) { + bool fromMM = false; + uchar *data = 0; + unsigned int data_len = 0; + +#ifdef QT_USE_MMAP + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif +#ifndef MAP_FAILED +#define MAP_FAILED -1 +#endif + + int fd = QT_OPEN(QFile::encodeName(f), O_RDONLY, +#if defined(Q_OS_WIN) + _S_IREAD | _S_IWRITE +#else + 0666 +#endif + ); + if (fd >= 0) { + QT_STATBUF st; + if (!QT_FSTAT(fd, &st)) { + uchar *ptr; + ptr = reinterpret_cast<uchar *>( + mmap(0, st.st_size, // any address, whole file + PROT_READ, // read-only memory + MAP_FILE | MAP_PRIVATE, // swap-backed map from file + fd, 0)); // from offset 0 of fd + if (ptr && ptr != reinterpret_cast<uchar *>(MAP_FAILED)) { + data = ptr; + data_len = st.st_size; + fromMM = true; + } + } + ::close(fd); + } +#endif // QT_USE_MMAP + if(!data) { + QFile file(f); + if (!file.exists()) + return false; + data_len = file.size(); + data = new uchar[data_len]; + + bool ok = false; + if (file.open(QIODevice::ReadOnly)) + ok = (data_len == (uint)file.read((char*)data, data_len)); + if (!ok) { + delete [] data; + data = 0; + data_len = 0; + return false; + } + fromMM = false; + } + if(data && QDynamicBufferResourceRoot::registerSelf(data)) { + if(fromMM) { + unmapPointer = data; + unmapLength = data_len; + } + fileName = f; + return true; + } + return false; + } +}; + +static QString qt_resource_fixResourceRoot(QString r) { + if(!r.isEmpty()) { + if(r.startsWith(QLatin1Char(':'))) + r = r.mid(1); + if(!r.isEmpty()) + r = QDir::cleanPath(r); + } + return r; +} + + +/*! + \fn bool QResource::registerResource(const QString &rccFileName, const QString &mapRoot) + + Registers the resource with the given \a rccFileName at the location in the + resource tree specified by \a mapRoot, and returns true if the file is + successfully opened; otherwise returns false. + + \sa unregisterResource() +*/ + +bool +QResource::registerResource(const QString &rccFilename, const QString &resourceRoot) +{ + QString r = qt_resource_fixResourceRoot(resourceRoot); + if(!r.isEmpty() && r[0] != QLatin1Char('/')) { + qWarning("QDir::registerResource: Registering a resource [%s] must be rooted in an absolute path (start with /) [%s]", + rccFilename.toLocal8Bit().data(), resourceRoot.toLocal8Bit().data()); + return false; + } + + QDynamicFileResourceRoot *root = new QDynamicFileResourceRoot(r); + if(root->registerSelf(rccFilename)) { + root->ref.ref(); + QMutexLocker lock(resourceMutex()); + resourceList()->append(root); + return true; + } + delete root; + return false; +} + +/*! + \fn bool QResource::unregisterResource(const QString &rccFileName, const QString &mapRoot) + + Unregisters the resource with the given \a rccFileName at the location in + the resource tree specified by \a mapRoot, and returns true if the + resource is successfully unloaded and no references exist for the + resource; otherwise returns false. + + \sa registerResource() +*/ + +bool +QResource::unregisterResource(const QString &rccFilename, const QString &resourceRoot) +{ + QString r = qt_resource_fixResourceRoot(resourceRoot); + + QMutexLocker lock(resourceMutex()); + ResourceList *list = resourceList(); + for(int i = 0; i < list->size(); ++i) { + QResourceRoot *res = list->at(i); + if(res->type() == QResourceRoot::Resource_File) { + QDynamicFileResourceRoot *root = reinterpret_cast<QDynamicFileResourceRoot*>(res); + if(root->mappingFile() == rccFilename && root->mappingRoot() == r) { + resourceList()->removeAt(i); + if(!root->ref.deref()) { + delete root; + return true; + } + return false; + } + } + } + return false; +} + + +/*! + \fn bool QResource::registerResource(const uchar *rccData, const QString &mapRoot) + \since 4.3 + + Registers the resource with the given \a rccData at the location in the + resource tree specified by \a mapRoot, and returns true if the file is + successfully opened; otherwise returns false. + + \warning The data must remain valid throughout the life of any QFile + that may reference the resource data. + + \sa unregisterResource() +*/ + +bool +QResource::registerResource(const uchar *rccData, const QString &resourceRoot) +{ + QString r = qt_resource_fixResourceRoot(resourceRoot); + if(!r.isEmpty() && r[0] != QLatin1Char('/')) { + qWarning("QDir::registerResource: Registering a resource [%p] must be rooted in an absolute path (start with /) [%s]", + rccData, resourceRoot.toLocal8Bit().data()); + return false; + } + + QDynamicBufferResourceRoot *root = new QDynamicBufferResourceRoot(r); + if(root->registerSelf(rccData)) { + root->ref.ref(); + QMutexLocker lock(resourceMutex()); + resourceList()->append(root); + return true; + } + delete root; + return false; +} + +/*! + \fn bool QResource::unregisterResource(const uchar *rccData, const QString &mapRoot) + \since 4.3 + + Unregisters the resource with the given \a rccData at the location in the + resource tree specified by \a mapRoot, and returns true if the resource is + successfully unloaded and no references exist into the resource; otherwise returns false. + + \sa registerResource() +*/ + +bool +QResource::unregisterResource(const uchar *rccData, const QString &resourceRoot) +{ + QString r = qt_resource_fixResourceRoot(resourceRoot); + + QMutexLocker lock(resourceMutex()); + ResourceList *list = resourceList(); + for(int i = 0; i < list->size(); ++i) { + QResourceRoot *res = list->at(i); + if(res->type() == QResourceRoot::Resource_Buffer) { + QDynamicBufferResourceRoot *root = reinterpret_cast<QDynamicBufferResourceRoot*>(res); + if(root->mappingBuffer() == rccData && root->mappingRoot() == r) { + resourceList()->removeAt(i); + if(!root->ref.deref()) { + delete root; + return true; + } + return false; + } + } + } + return false; +} + +//resource engine +class QResourceFileEnginePrivate : public QAbstractFileEnginePrivate +{ +protected: + Q_DECLARE_PUBLIC(QResourceFileEngine) +private: + uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags); + bool unmap(uchar *ptr); + qint64 offset; + QResource resource; + QByteArray uncompressed; +protected: + QResourceFileEnginePrivate() : offset(0) { } +}; + +bool QResourceFileEngine::mkdir(const QString &, bool) const +{ + return false; +} + +bool QResourceFileEngine::rmdir(const QString &, bool) const +{ + return false; +} + +bool QResourceFileEngine::setSize(qint64) +{ + return false; +} + +QStringList QResourceFileEngine::entryList(QDir::Filters filters, const QStringList &filterNames) const +{ + return QAbstractFileEngine::entryList(filters, filterNames); +} + +bool QResourceFileEngine::caseSensitive() const +{ + return true; +} + +QResourceFileEngine::QResourceFileEngine(const QString &file) : + QAbstractFileEngine(*new QResourceFileEnginePrivate) +{ + Q_D(QResourceFileEngine); + d->resource.setFileName(file); + if(d->resource.isCompressed() && d->resource.size()) { +#ifndef QT_NO_COMPRESS + d->uncompressed = qUncompress(d->resource.data(), d->resource.size()); +#else + Q_ASSERT(!"QResourceFileEngine::open: Qt built without support for compression"); +#endif + } +} + +QResourceFileEngine::~QResourceFileEngine() +{ +} + +void QResourceFileEngine::setFileName(const QString &file) +{ + Q_D(QResourceFileEngine); + d->resource.setFileName(file); +} + +bool QResourceFileEngine::open(QIODevice::OpenMode flags) +{ + Q_D(QResourceFileEngine); + if (d->resource.fileName().isEmpty()) { + qWarning("QResourceFileEngine::open: Missing file name"); + return false; + } + if(flags & QIODevice::WriteOnly) + return false; + if(!d->resource.isValid()) + return false; + return true; +} + +bool QResourceFileEngine::close() +{ + Q_D(QResourceFileEngine); + d->offset = 0; + d->uncompressed.clear(); + return true; +} + +bool QResourceFileEngine::flush() +{ + return true; +} + +qint64 QResourceFileEngine::read(char *data, qint64 len) +{ + Q_D(QResourceFileEngine); + if(len > size()-d->offset) + len = size()-d->offset; + if(len <= 0) + return 0; + if(d->resource.isCompressed()) + memcpy(data, d->uncompressed.constData()+d->offset, len); + else + memcpy(data, d->resource.data()+d->offset, len); + d->offset += len; + return len; +} + +qint64 QResourceFileEngine::write(const char *, qint64) +{ + return -1; +} + +bool QResourceFileEngine::remove() +{ + return false; +} + +bool QResourceFileEngine::copy(const QString &) +{ + return false; +} + +bool QResourceFileEngine::rename(const QString &) +{ + return false; +} + +bool QResourceFileEngine::link(const QString &) +{ + return false; +} + +qint64 QResourceFileEngine::size() const +{ + Q_D(const QResourceFileEngine); + if(!d->resource.isValid()) + return 0; + if(d->resource.isCompressed()) + return d->uncompressed.size(); + return d->resource.size(); +} + +qint64 QResourceFileEngine::pos() const +{ + Q_D(const QResourceFileEngine); + return d->offset; +} + +bool QResourceFileEngine::atEnd() const +{ + Q_D(const QResourceFileEngine); + if(!d->resource.isValid()) + return true; + return d->offset == size(); +} + +bool QResourceFileEngine::seek(qint64 pos) +{ + Q_D(QResourceFileEngine); + if(!d->resource.isValid()) + return false; + + if(d->offset > size()) + return false; + d->offset = pos; + return true; +} + +bool QResourceFileEngine::isSequential() const +{ + return false; +} + +QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const +{ + Q_D(const QResourceFileEngine); + QAbstractFileEngine::FileFlags ret = 0; + if(!d->resource.isValid()) + return ret; + + if(type & PermsMask) + ret |= QAbstractFileEngine::FileFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm); + if(type & TypesMask) { + if(d->resource.isDir()) + ret |= DirectoryType; + else + ret |= FileType; + } + if(type & FlagsMask) { + ret |= ExistsFlag; + if(d->resource.absoluteFilePath() == QLatin1String(":/")) + ret |= RootFlag; + } + return ret; +} + +bool QResourceFileEngine::setPermissions(uint) +{ + return false; +} + +QString QResourceFileEngine::fileName(FileName file) const +{ + Q_D(const QResourceFileEngine); + if(file == BaseName) { + int slash = d->resource.fileName().lastIndexOf(QLatin1Char('/')); + if (slash == -1) + return d->resource.fileName(); + return d->resource.fileName().mid(slash + 1); + } else if(file == PathName || file == AbsolutePathName) { + const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath() : d->resource.fileName(); + const int slash = path.lastIndexOf(QLatin1Char('/')); + if (slash == -1) + return QLatin1String(":"); + else if (slash <= 1) + return QLatin1String(":/"); + return path.left(slash); + + } else if(file == CanonicalName || file == CanonicalPathName) { + const QString absoluteFilePath = d->resource.absoluteFilePath(); + if(file == CanonicalPathName) { + const int slash = absoluteFilePath.lastIndexOf(QLatin1Char('/')); + if (slash != -1) + return absoluteFilePath.left(slash); + } + return absoluteFilePath; + } + return d->resource.fileName(); +} + +bool QResourceFileEngine::isRelativePath() const +{ + return false; +} + +uint QResourceFileEngine::ownerId(FileOwner) const +{ + static const uint nobodyID = (uint) -2; + return nobodyID; +} + +QString QResourceFileEngine::owner(FileOwner) const +{ + return QString(); +} + +QDateTime QResourceFileEngine::fileTime(FileTime) const +{ + return QDateTime(); +} + +/*! + \internal +*/ +QAbstractFileEngine::Iterator *QResourceFileEngine::beginEntryList(QDir::Filters filters, + const QStringList &filterNames) +{ + return new QResourceFileEngineIterator(filters, filterNames); +} + +/*! + \internal +*/ +QAbstractFileEngine::Iterator *QResourceFileEngine::endEntryList() +{ + return 0; +} + +bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) +{ + Q_D(QResourceFileEngine); + if (extension == MapExtension) { + const MapExtensionOption *options = (MapExtensionOption*)(option); + MapExtensionReturn *returnValue = static_cast<MapExtensionReturn*>(output); + returnValue->address = d->map(options->offset, options->size, options->flags); + return (returnValue->address != 0); + } + if (extension == UnMapExtension) { + UnMapExtensionOption *options = (UnMapExtensionOption*)option; + return d->unmap(options->address); + } + return false; +} + +bool QResourceFileEngine::supportsExtension(Extension extension) const +{ + return (extension == UnMapExtension || extension == MapExtension); +} + +uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags) +{ + Q_Q(QResourceFileEngine); + Q_UNUSED(flags); + if (offset < 0 || size <= 0 || !resource.isValid() || offset + size > resource.size()) { + q->setError(QFile::UnspecifiedError, QString()); + return 0; + } + uchar *address = const_cast<uchar *>(resource.data()); + return (address + offset); +} + +bool QResourceFileEnginePrivate::unmap(uchar *ptr) +{ + Q_UNUSED(ptr); + return true; +} + +Q_CORE_EXPORT void qInitResourceIO() { } // ### Qt 5: remove + +QT_END_NAMESPACE diff --git a/src/corelib/io/qresource.h b/src/corelib/io/qresource.h new file mode 100644 index 0000000000..ba993c4361 --- /dev/null +++ b/src/corelib/io/qresource.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRESOURCE_H +#define QRESOURCE_H + +#include <QtCore/qstring.h> +#include <QtCore/qlocale.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QResourcePrivate; + +class Q_CORE_EXPORT QResource +{ +public: + QResource(const QString &file=QString(), const QLocale &locale=QLocale()); + ~QResource(); + + void setFileName(const QString &file); + QString fileName() const; + QString absoluteFilePath() const; + + void setLocale(const QLocale &locale); + QLocale locale() const; + + bool isValid() const; + + bool isCompressed() const; + qint64 size() const; + const uchar *data() const; + + static void addSearchPath(const QString &path); + static QStringList searchPaths(); + + static bool registerResource(const QString &rccFilename, const QString &resourceRoot=QString()); + static bool unregisterResource(const QString &rccFilename, const QString &resourceRoot=QString()); + + static bool registerResource(const uchar *rccData, const QString &resourceRoot=QString()); + static bool unregisterResource(const uchar *rccData, const QString &resourceRoot=QString()); + +protected: + friend class QResourceFileEngine; + friend class QResourceFileEngineIterator; + bool isDir() const; + inline bool isFile() const { return !isDir(); } + QStringList children() const; + +protected: + QScopedPointer<QResourcePrivate> d_ptr; + +private: + Q_DECLARE_PRIVATE(QResource) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QRESOURCE_H diff --git a/src/corelib/io/qresource_iterator.cpp b/src/corelib/io/qresource_iterator.cpp new file mode 100644 index 0000000000..b49f228248 --- /dev/null +++ b/src/corelib/io/qresource_iterator.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qresource.h" +#include "qresource_iterator_p.h" + +#include <QtCore/qvariant.h> + +QT_BEGIN_NAMESPACE + +QResourceFileEngineIterator::QResourceFileEngineIterator(QDir::Filters filters, + const QStringList &filterNames) + : QAbstractFileEngineIterator(filters, filterNames), index(-1) +{ +} + +QResourceFileEngineIterator::~QResourceFileEngineIterator() +{ +} + +QString QResourceFileEngineIterator::next() +{ + if (!hasNext()) + return QString(); + ++index; + return currentFilePath(); +} + +bool QResourceFileEngineIterator::hasNext() const +{ + if (index == -1) { + // Lazy initialization of the iterator + QResource resource(path()); + if (!resource.isValid()) + return false; + + // Initialize and move to the next entry. + entries = resource.children(); + index = 0; + } + + return index < entries.size(); +} + +QString QResourceFileEngineIterator::currentFileName() const +{ + if (index <= 0 || index > entries.size()) + return QString(); + return entries.at(index - 1); +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qresource_iterator_p.h b/src/corelib/io/qresource_iterator_p.h new file mode 100644 index 0000000000..2f647a6695 --- /dev/null +++ b/src/corelib/io/qresource_iterator_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRESOURCE_ITERATOR_P_H +#define QRESOURCE_ITERATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qabstractfileengine.h" +#include "qdir.h" + +QT_BEGIN_NAMESPACE + +class QResourceFileEngineIteratorPrivate; +class QResourceFileEngineIterator : public QAbstractFileEngineIterator +{ +public: + QResourceFileEngineIterator(QDir::Filters filters, const QStringList &filterNames); + ~QResourceFileEngineIterator(); + + QString next(); + bool hasNext() const; + + QString currentFileName() const; + +private: + mutable QStringList entries; + mutable int index; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/corelib/io/qresource_p.h b/src/corelib/io/qresource_p.h new file mode 100644 index 0000000000..b7218c9377 --- /dev/null +++ b/src/corelib/io/qresource_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRESOURCE_P_H +#define QRESOURCE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qabstractfileengine.h" + +QT_BEGIN_NAMESPACE + +class QResourceFileEnginePrivate; +class QResourceFileEngine : public QAbstractFileEngine +{ +private: + Q_DECLARE_PRIVATE(QResourceFileEngine) +public: + explicit QResourceFileEngine(const QString &path); + ~QResourceFileEngine(); + + virtual void setFileName(const QString &file); + + virtual bool open(QIODevice::OpenMode flags) ; + virtual bool close(); + virtual bool flush(); + virtual qint64 size() const; + virtual qint64 pos() const; + virtual bool atEnd() const; + virtual bool seek(qint64); + virtual qint64 read(char *data, qint64 maxlen); + virtual qint64 write(const char *data, qint64 len); + + virtual bool remove(); + virtual bool copy(const QString &newName); + virtual bool rename(const QString &newName); + virtual bool link(const QString &newName); + + virtual bool isSequential() const; + + virtual bool isRelativePath() const; + + virtual bool mkdir(const QString &dirName, bool createParentDirectories) const; + virtual bool rmdir(const QString &dirName, bool recurseParentDirectories) const; + + virtual bool setSize(qint64 size); + + virtual QStringList entryList(QDir::Filters filters, const QStringList &filterNames) const; + + virtual bool caseSensitive() const; + + virtual FileFlags fileFlags(FileFlags type) const; + + virtual bool setPermissions(uint perms); + + virtual QString fileName(QAbstractFileEngine::FileName file) const; + + virtual uint ownerId(FileOwner) const; + virtual QString owner(FileOwner) const; + + virtual QDateTime fileTime(FileTime time) const; + + virtual Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames); + virtual Iterator *endEntryList(); + + bool extension(Extension extension, const ExtensionOption *option = 0, ExtensionReturn *output = 0); + bool supportsExtension(Extension extension) const; +}; + +QT_END_NAMESPACE + +#endif // QRESOURCE_P_H diff --git a/src/corelib/io/qsettings.cpp b/src/corelib/io/qsettings.cpp new file mode 100644 index 0000000000..b084ca5d62 --- /dev/null +++ b/src/corelib/io/qsettings.cpp @@ -0,0 +1,3843 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qdebug.h> +#include "qplatformdefs.h" +#include "qsettings.h" + +#ifndef QT_NO_SETTINGS + +#include "qsettings_p.h" +#include "qcache.h" +#include "qfile.h" +#include "qdir.h" +#include "qfileinfo.h" +#include "qmutex.h" +#include "qlibraryinfo.h" +#include "qtemporaryfile.h" + +#ifndef QT_NO_TEXTCODEC +# include "qtextcodec.h" +#endif + +#ifndef QT_NO_GEOM_VARIANT +#include "qsize.h" +#include "qpoint.h" +#include "qrect.h" +#endif // !QT_NO_GEOM_VARIANT + +#ifndef QT_NO_QOBJECT +#include "qcoreapplication.h" + +#ifdef Q_OS_WIN // for homedirpath reading from registry +#include "qt_windows.h" +#include <private/qsystemlibrary_p.h> + +#endif // Q_OS_WIN +#endif // QT_NO_QOBJECT + +#ifdef Q_OS_VXWORKS +# include <ioLib.h> +#endif + +#include <stdlib.h> + +#ifndef CSIDL_COMMON_APPDATA +#define CSIDL_COMMON_APPDATA 0x0023 // All Users\Application Data +#endif + +#ifndef CSIDL_APPDATA +#define CSIDL_APPDATA 0x001a // <username>\Application Data +#endif + +#ifdef Q_AUTOTEST_EXPORT +# define Q_AUTOTEST_EXPORT_HELPER Q_AUTOTEST_EXPORT +#else +# define Q_AUTOTEST_EXPORT_HELPER static +#endif + +// ************************************************************************ +// QConfFile + +/* + QConfFile objects are explicitly shared within the application. + This ensures that modification to the settings done through one + QSettings object are immediately reflected in other setting + objects of the same application. +*/ + +QT_BEGIN_NAMESPACE + +struct QConfFileCustomFormat +{ + QString extension; + QSettings::ReadFunc readFunc; + QSettings::WriteFunc writeFunc; + Qt::CaseSensitivity caseSensitivity; +}; + +typedef QHash<QString, QConfFile *> ConfFileHash; +typedef QCache<QString, QConfFile> ConfFileCache; +typedef QHash<int, QString> PathHash; +typedef QVector<QConfFileCustomFormat> CustomFormatVector; + +Q_GLOBAL_STATIC(ConfFileHash, usedHashFunc) +Q_GLOBAL_STATIC(ConfFileCache, unusedCacheFunc) +Q_GLOBAL_STATIC(PathHash, pathHashFunc) +Q_GLOBAL_STATIC(CustomFormatVector, customFormatVectorFunc) +Q_GLOBAL_STATIC(QMutex, globalMutex) +static QSettings::Format globalDefaultFormat = QSettings::NativeFormat; + +#ifndef Q_OS_WIN +inline bool qt_isEvilFsTypeName(const char *name) +{ + return (qstrncmp(name, "nfs", 3) == 0 + || qstrncmp(name, "autofs", 6) == 0 + || qstrncmp(name, "cachefs", 7) == 0); +} + +#if defined(Q_OS_BSD4) && !defined(Q_OS_NETBSD) +QT_BEGIN_INCLUDE_NAMESPACE +# include <sys/param.h> +# include <sys/mount.h> +QT_END_INCLUDE_NAMESPACE + +Q_AUTOTEST_EXPORT_HELPER bool qIsLikelyToBeNfs(int handle) +{ + struct statfs buf; + if (fstatfs(handle, &buf) != 0) + return false; + return qt_isEvilFsTypeName(buf.f_fstypename); +} + +#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD) +QT_BEGIN_INCLUDE_NAMESPACE +# include <sys/vfs.h> +# ifdef QT_LINUXBASE + // LSB 3.2 has fstatfs in sys/statfs.h, sys/vfs.h is just an empty dummy header +# include <sys/statfs.h> +# endif +QT_END_INCLUDE_NAMESPACE +# ifndef NFS_SUPER_MAGIC +# define NFS_SUPER_MAGIC 0x00006969 +# endif +# ifndef AUTOFS_SUPER_MAGIC +# define AUTOFS_SUPER_MAGIC 0x00000187 +# endif +# ifndef AUTOFSNG_SUPER_MAGIC +# define AUTOFSNG_SUPER_MAGIC 0x7d92b1a0 +# endif + +Q_AUTOTEST_EXPORT_HELPER bool qIsLikelyToBeNfs(int handle) +{ + struct statfs buf; + if (fstatfs(handle, &buf) != 0) + return false; + return buf.f_type == NFS_SUPER_MAGIC + || buf.f_type == AUTOFS_SUPER_MAGIC + || buf.f_type == AUTOFSNG_SUPER_MAGIC; +} + +#elif defined(Q_OS_SOLARIS) || defined(Q_OS_IRIX) || defined(Q_OS_AIX) || defined(Q_OS_HPUX) \ + || defined(Q_OS_OSF) || defined(Q_OS_QNX) || defined(Q_OS_SCO) \ + || defined(Q_OS_UNIXWARE) || defined(Q_OS_RELIANT) || defined(Q_OS_NETBSD) +QT_BEGIN_INCLUDE_NAMESPACE +# include <sys/statvfs.h> +QT_END_INCLUDE_NAMESPACE + +Q_AUTOTEST_EXPORT_HELPER bool qIsLikelyToBeNfs(int handle) +{ + struct statvfs buf; + if (fstatvfs(handle, &buf) != 0) + return false; +#if defined(Q_OS_NETBSD) + return qt_isEvilFsTypeName(buf.f_fstypename); +#else + return qt_isEvilFsTypeName(buf.f_basetype); +#endif +} +#else +Q_AUTOTEST_EXPORT_HELPER inline bool qIsLikelyToBeNfs(int /* handle */) +{ + return true; +} +#endif + +static bool unixLock(int handle, int lockType) +{ + /* + NFS hangs on the fcntl() call below when statd or lockd isn't + running. There's no way to detect this. Our work-around for + now is to disable locking when we detect NFS (or AutoFS or + CacheFS, which are probably wrapping NFS). + */ + if (qIsLikelyToBeNfs(handle)) + return false; + + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = lockType; + return fcntl(handle, F_SETLKW, &fl) == 0; +} +#endif + +QConfFile::QConfFile(const QString &fileName, bool _userPerms) + : name(fileName), size(0), ref(1), userPerms(_userPerms) +{ + usedHashFunc()->insert(name, this); +} + +QConfFile::~QConfFile() +{ + if (usedHashFunc()) + usedHashFunc()->remove(name); +} + +ParsedSettingsMap QConfFile::mergedKeyMap() const +{ + ParsedSettingsMap result = originalKeys; + ParsedSettingsMap::const_iterator i; + + for (i = removedKeys.begin(); i != removedKeys.end(); ++i) + result.remove(i.key()); + for (i = addedKeys.begin(); i != addedKeys.end(); ++i) + result.insert(i.key(), i.value()); + return result; +} + +bool QConfFile::isWritable() const +{ + QFileInfo fileInfo(name); + +#ifndef QT_NO_TEMPORARYFILE + if (fileInfo.exists()) { +#endif + QFile file(name); + return file.open(QFile::ReadWrite); +#ifndef QT_NO_TEMPORARYFILE + } else { + // Create the directories to the file. + QDir dir(fileInfo.absolutePath()); + if (!dir.exists()) { + if (!dir.mkpath(dir.absolutePath())) + return false; + } + + // we use a temporary file to avoid race conditions + QTemporaryFile file(name); + return file.open(); + } +#endif +} + +QConfFile *QConfFile::fromName(const QString &fileName, bool _userPerms) +{ + QString absPath = QFileInfo(fileName).absoluteFilePath(); + + ConfFileHash *usedHash = usedHashFunc(); + ConfFileCache *unusedCache = unusedCacheFunc(); + + QConfFile *confFile = 0; + QMutexLocker locker(globalMutex()); + + if (!(confFile = usedHash->value(absPath))) { + if ((confFile = unusedCache->take(absPath))) + usedHash->insert(absPath, confFile); + } + if (confFile) { + confFile->ref.ref(); + return confFile; + } + return new QConfFile(absPath, _userPerms); +} + +void QConfFile::clearCache() +{ + QMutexLocker locker(globalMutex()); + unusedCacheFunc()->clear(); +} + +// ************************************************************************ +// QSettingsPrivate + +QSettingsPrivate::QSettingsPrivate(QSettings::Format format) + : format(format), scope(QSettings::UserScope /* nothing better to put */), iniCodec(0), spec(0), fallbacks(true), + pendingChanges(false), status(QSettings::NoError) +{ +} + +QSettingsPrivate::QSettingsPrivate(QSettings::Format format, QSettings::Scope scope, + const QString &organization, const QString &application) + : format(format), scope(scope), organizationName(organization), applicationName(application), + iniCodec(0), spec(0), fallbacks(true), pendingChanges(false), status(QSettings::NoError) +{ +} + +QSettingsPrivate::~QSettingsPrivate() +{ +} + +QString QSettingsPrivate::actualKey(const QString &key) const +{ + QString n = normalizedKey(key); + Q_ASSERT_X(!n.isEmpty(), "QSettings", "empty key"); + n.prepend(groupPrefix); + return n; +} + +/* + Returns a string that never starts nor ends with a slash (or an + empty string). Examples: + + "foo" becomes "foo" + "/foo//bar///" becomes "foo/bar" + "///" becomes "" + + This function is optimized to avoid a QString deep copy in the + common case where the key is already normalized. +*/ +QString QSettingsPrivate::normalizedKey(const QString &key) +{ + QString result = key; + + int i = 0; + while (i < result.size()) { + while (result.at(i) == QLatin1Char('/')) { + result.remove(i, 1); + if (i == result.size()) + goto after_loop; + } + while (result.at(i) != QLatin1Char('/')) { + ++i; + if (i == result.size()) + return result; + } + ++i; // leave the slash alone + } + +after_loop: + if (!result.isEmpty()) + result.truncate(i - 1); // remove the trailing slash + return result; +} + +// see also qsettings_win.cpp and qsettings_mac.cpp + +#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) +QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope, + const QString &organization, const QString &application) +{ + return new QConfFileSettingsPrivate(format, scope, organization, application); +} +#endif + +#if !defined(Q_OS_WIN) +QSettingsPrivate *QSettingsPrivate::create(const QString &fileName, QSettings::Format format) +{ + return new QConfFileSettingsPrivate(fileName, format); +} +#endif + +void QSettingsPrivate::processChild(QString key, ChildSpec spec, QMap<QString, QString> &result) +{ + if (spec != AllKeys) { + int slashPos = key.indexOf(QLatin1Char('/')); + if (slashPos == -1) { + if (spec != ChildKeys) + return; + } else { + if (spec != ChildGroups) + return; + key.truncate(slashPos); + } + } + result.insert(key, QString()); +} + +void QSettingsPrivate::beginGroupOrArray(const QSettingsGroup &group) +{ + groupStack.push(group); + if (!group.name().isEmpty()) { + groupPrefix += group.name(); + groupPrefix += QLatin1Char('/'); + } +} + +/* + We only set an error if there isn't one set already. This way the user always gets the + first error that occurred. We always allow clearing errors. +*/ + +void QSettingsPrivate::setStatus(QSettings::Status status) const +{ + if (status == QSettings::NoError || this->status == QSettings::NoError) + this->status = status; +} + +void QSettingsPrivate::update() +{ + flush(); + pendingChanges = false; +} + +void QSettingsPrivate::requestUpdate() +{ + if (!pendingChanges) { + pendingChanges = true; +#ifndef QT_NO_QOBJECT + Q_Q(QSettings); + QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest)); +#else + update(); +#endif + } +} + +QStringList QSettingsPrivate::variantListToStringList(const QVariantList &l) +{ + QStringList result; + QVariantList::const_iterator it = l.constBegin(); + for (; it != l.constEnd(); ++it) + result.append(variantToString(*it)); + return result; +} + +QVariant QSettingsPrivate::stringListToVariantList(const QStringList &l) +{ + QStringList outStringList = l; + for (int i = 0; i < outStringList.count(); ++i) { + const QString &str = outStringList.at(i); + + if (str.startsWith(QLatin1Char('@'))) { + if (str.length() >= 2 && str.at(1) == QLatin1Char('@')) { + outStringList[i].remove(0, 1); + } else { + QVariantList variantList; + for (int j = 0; j < l.count(); ++j) + variantList.append(stringToVariant(l.at(j))); + return variantList; + } + } + } + return outStringList; +} + +QString QSettingsPrivate::variantToString(const QVariant &v) +{ + QString result; + + switch (v.type()) { + case QVariant::Invalid: + result = QLatin1String("@Invalid()"); + break; + + case QVariant::ByteArray: { + QByteArray a = v.toByteArray(); + result = QLatin1String("@ByteArray("); + result += QString::fromLatin1(a.constData(), a.size()); + result += QLatin1Char(')'); + break; + } + + case QVariant::String: + case QVariant::LongLong: + case QVariant::ULongLong: + case QVariant::Int: + case QVariant::UInt: + case QVariant::Bool: + case QVariant::Double: + case QVariant::KeySequence: { + result = v.toString(); + if (result.startsWith(QLatin1Char('@'))) + result.prepend(QLatin1Char('@')); + break; + } +#ifndef QT_NO_GEOM_VARIANT + case QVariant::Rect: { + QRect r = qvariant_cast<QRect>(v); + result += QLatin1String("@Rect("); + result += QString::number(r.x()); + result += QLatin1Char(' '); + result += QString::number(r.y()); + result += QLatin1Char(' '); + result += QString::number(r.width()); + result += QLatin1Char(' '); + result += QString::number(r.height()); + result += QLatin1Char(')'); + break; + } + case QVariant::Size: { + QSize s = qvariant_cast<QSize>(v); + result += QLatin1String("@Size("); + result += QString::number(s.width()); + result += QLatin1Char(' '); + result += QString::number(s.height()); + result += QLatin1Char(')'); + break; + } + case QVariant::Point: { + QPoint p = qvariant_cast<QPoint>(v); + result += QLatin1String("@Point("); + result += QString::number(p.x()); + result += QLatin1Char(' '); + result += QString::number(p.y()); + result += QLatin1Char(')'); + break; + } +#endif // !QT_NO_GEOM_VARIANT + + default: { +#ifndef QT_NO_DATASTREAM + QByteArray a; + { + QDataStream s(&a, QIODevice::WriteOnly); + s.setVersion(QDataStream::Qt_4_0); + s << v; + } + + result = QLatin1String("@Variant("); + result += QString::fromLatin1(a.constData(), a.size()); + result += QLatin1Char(')'); +#else + Q_ASSERT(!"QSettings: Cannot save custom types without QDataStream support"); +#endif + break; + } + } + + return result; +} + + +QVariant QSettingsPrivate::stringToVariant(const QString &s) +{ + if (s.startsWith(QLatin1Char('@'))) { + if (s.endsWith(QLatin1Char(')'))) { + if (s.startsWith(QLatin1String("@ByteArray("))) { + return QVariant(s.toLatin1().mid(11, s.size() - 12)); + } else if (s.startsWith(QLatin1String("@Variant("))) { +#ifndef QT_NO_DATASTREAM + QByteArray a(s.toLatin1().mid(9)); + QDataStream stream(&a, QIODevice::ReadOnly); + stream.setVersion(QDataStream::Qt_4_0); + QVariant result; + stream >> result; + return result; +#else + Q_ASSERT(!"QSettings: Cannot load custom types without QDataStream support"); +#endif +#ifndef QT_NO_GEOM_VARIANT + } else if (s.startsWith(QLatin1String("@Rect("))) { + QStringList args = QSettingsPrivate::splitArgs(s, 5); + if (args.size() == 4) + return QVariant(QRect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt())); + } else if (s.startsWith(QLatin1String("@Size("))) { + QStringList args = QSettingsPrivate::splitArgs(s, 5); + if (args.size() == 2) + return QVariant(QSize(args[0].toInt(), args[1].toInt())); + } else if (s.startsWith(QLatin1String("@Point("))) { + QStringList args = QSettingsPrivate::splitArgs(s, 6); + if (args.size() == 2) + return QVariant(QPoint(args[0].toInt(), args[1].toInt())); +#endif + } else if (s == QLatin1String("@Invalid()")) { + return QVariant(); + } + + } + if (s.startsWith(QLatin1String("@@"))) + return QVariant(s.mid(1)); + } + + return QVariant(s); +} + +static const char hexDigits[] = "0123456789ABCDEF"; + +void QSettingsPrivate::iniEscapedKey(const QString &key, QByteArray &result) +{ + result.reserve(result.length() + key.length() * 3 / 2); + for (int i = 0; i < key.size(); ++i) { + uint ch = key.at(i).unicode(); + + if (ch == '/') { + result += '\\'; + } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') + || ch == '_' || ch == '-' || ch == '.') { + result += (char)ch; + } else if (ch <= 0xFF) { + result += '%'; + result += hexDigits[ch / 16]; + result += hexDigits[ch % 16]; + } else { + result += "%U"; + QByteArray hexCode; + for (int i = 0; i < 4; ++i) { + hexCode.prepend(hexDigits[ch % 16]); + ch >>= 4; + } + result += hexCode; + } + } +} + +bool QSettingsPrivate::iniUnescapedKey(const QByteArray &key, int from, int to, QString &result) +{ + bool lowercaseOnly = true; + int i = from; + result.reserve(result.length() + (to - from)); + while (i < to) { + int ch = (uchar)key.at(i); + + if (ch == '\\') { + result += QLatin1Char('/'); + ++i; + continue; + } + + if (ch != '%' || i == to - 1) { + if (uint(ch - 'A') <= 'Z' - 'A') // only for ASCII + lowercaseOnly = false; + result += QLatin1Char(ch); + ++i; + continue; + } + + int numDigits = 2; + int firstDigitPos = i + 1; + + ch = key.at(i + 1); + if (ch == 'U') { + ++firstDigitPos; + numDigits = 4; + } + + if (firstDigitPos + numDigits > to) { + result += QLatin1Char('%'); + // ### missing U + ++i; + continue; + } + + bool ok; + ch = key.mid(firstDigitPos, numDigits).toInt(&ok, 16); + if (!ok) { + result += QLatin1Char('%'); + // ### missing U + ++i; + continue; + } + + QChar qch(ch); + if (qch.isUpper()) + lowercaseOnly = false; + result += qch; + i = firstDigitPos + numDigits; + } + return lowercaseOnly; +} + +void QSettingsPrivate::iniEscapedString(const QString &str, QByteArray &result, QTextCodec *codec) +{ + bool needsQuotes = false; + bool escapeNextIfDigit = false; + int i; + int startPos = result.size(); + + result.reserve(startPos + str.size() * 3 / 2); + for (i = 0; i < str.size(); ++i) { + uint ch = str.at(i).unicode(); + if (ch == ';' || ch == ',' || ch == '=') + needsQuotes = true; + + if (escapeNextIfDigit + && ((ch >= '0' && ch <= '9') + || (ch >= 'a' && ch <= 'f') + || (ch >= 'A' && ch <= 'F'))) { + result += "\\x"; + result += QByteArray::number(ch, 16); + continue; + } + + escapeNextIfDigit = false; + + switch (ch) { + case '\0': + result += "\\0"; + escapeNextIfDigit = true; + break; + case '\a': + result += "\\a"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + case '\v': + result += "\\v"; + break; + case '"': + case '\\': + result += '\\'; + result += (char)ch; + break; + default: + if (ch <= 0x1F || (ch >= 0x7F && !codec)) { + result += "\\x"; + result += QByteArray::number(ch, 16); + escapeNextIfDigit = true; +#ifndef QT_NO_TEXTCODEC + } else if (codec) { + // slow + result += codec->fromUnicode(str.at(i)); +#endif + } else { + result += (char)ch; + } + } + } + + if (needsQuotes + || (startPos < result.size() && (result.at(startPos) == ' ' + || result.at(result.size() - 1) == ' '))) { + result.insert(startPos, '"'); + result += '"'; + } +} + +inline static void iniChopTrailingSpaces(QString &str) +{ + int n = str.size() - 1; + QChar ch; + while (n >= 0 && ((ch = str.at(n)) == QLatin1Char(' ') || ch == QLatin1Char('\t'))) + str.truncate(n--); +} + +void QSettingsPrivate::iniEscapedStringList(const QStringList &strs, QByteArray &result, QTextCodec *codec) +{ + if (strs.isEmpty()) { + /* + We need to distinguish between empty lists and one-item + lists that contain an empty string. Ideally, we'd have a + @EmptyList() symbol but that would break compatibility + with Qt 4.0. @Invalid() stands for QVariant(), and + QVariant().toStringList() returns an empty QStringList, + so we're in good shape. + + ### Qt 5: Use a nicer syntax, e.g. @List, for variant lists + */ + result += "@Invalid()"; + } else { + for (int i = 0; i < strs.size(); ++i) { + if (i != 0) + result += ", "; + iniEscapedString(strs.at(i), result, codec); + } + } +} + +bool QSettingsPrivate::iniUnescapedStringList(const QByteArray &str, int from, int to, + QString &stringResult, QStringList &stringListResult, + QTextCodec *codec) +{ + static const char escapeCodes[][2] = + { + { 'a', '\a' }, + { 'b', '\b' }, + { 'f', '\f' }, + { 'n', '\n' }, + { 'r', '\r' }, + { 't', '\t' }, + { 'v', '\v' }, + { '"', '"' }, + { '?', '?' }, + { '\'', '\'' }, + { '\\', '\\' } + }; + static const int numEscapeCodes = sizeof(escapeCodes) / sizeof(escapeCodes[0]); + + bool isStringList = false; + bool inQuotedString = false; + bool currentValueIsQuoted = false; + int escapeVal = 0; + int i = from; + char ch; + +StSkipSpaces: + while (i < to && ((ch = str.at(i)) == ' ' || ch == '\t')) + ++i; + // fallthrough + +StNormal: + while (i < to) { + switch (str.at(i)) { + case '\\': + ++i; + if (i >= to) + goto end; + + ch = str.at(i++); + for (int j = 0; j < numEscapeCodes; ++j) { + if (ch == escapeCodes[j][0]) { + stringResult += QLatin1Char(escapeCodes[j][1]); + goto StNormal; + } + } + + if (ch == 'x') { + escapeVal = 0; + + if (i >= to) + goto end; + + ch = str.at(i); + if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) + goto StHexEscape; + } else if (ch >= '0' && ch <= '7') { + escapeVal = ch - '0'; + goto StOctEscape; + } else if (ch == '\n' || ch == '\r') { + if (i < to) { + char ch2 = str.at(i); + // \n, \r, \r\n, and \n\r are legitimate line terminators in INI files + if ((ch2 == '\n' || ch2 == '\r') && ch2 != ch) + ++i; + } + } else { + // the character is skipped + } + break; + case '"': + ++i; + currentValueIsQuoted = true; + inQuotedString = !inQuotedString; + if (!inQuotedString) + goto StSkipSpaces; + break; + case ',': + if (!inQuotedString) { + if (!currentValueIsQuoted) + iniChopTrailingSpaces(stringResult); + if (!isStringList) { + isStringList = true; + stringListResult.clear(); + stringResult.squeeze(); + } + stringListResult.append(stringResult); + stringResult.clear(); + currentValueIsQuoted = false; + ++i; + goto StSkipSpaces; + } + // fallthrough + default: { + int j = i + 1; + while (j < to) { + ch = str.at(j); + if (ch == '\\' || ch == '"' || ch == ',') + break; + ++j; + } + +#ifndef QT_NO_TEXTCODEC + if (codec) { + stringResult += codec->toUnicode(str.constData() + i, j - i); + } else +#endif + { + int n = stringResult.size(); + stringResult.resize(n + (j - i)); + QChar *resultData = stringResult.data() + n; + for (int k = i; k < j; ++k) + *resultData++ = QLatin1Char(str.at(k)); + } + i = j; + } + } + } + goto end; + +StHexEscape: + if (i >= to) { + stringResult += QChar(escapeVal); + goto end; + } + + ch = str.at(i); + if (ch >= 'a') + ch -= 'a' - 'A'; + if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) { + escapeVal <<= 4; + escapeVal += strchr(hexDigits, ch) - hexDigits; + ++i; + goto StHexEscape; + } else { + stringResult += QChar(escapeVal); + goto StNormal; + } + +StOctEscape: + if (i >= to) { + stringResult += QChar(escapeVal); + goto end; + } + + ch = str.at(i); + if (ch >= '0' && ch <= '7') { + escapeVal <<= 3; + escapeVal += ch - '0'; + ++i; + goto StOctEscape; + } else { + stringResult += QChar(escapeVal); + goto StNormal; + } + +end: + if (!currentValueIsQuoted) + iniChopTrailingSpaces(stringResult); + if (isStringList) + stringListResult.append(stringResult); + return isStringList; +} + +QStringList QSettingsPrivate::splitArgs(const QString &s, int idx) +{ + int l = s.length(); + Q_ASSERT(l > 0); + Q_ASSERT(s.at(idx) == QLatin1Char('(')); + Q_ASSERT(s.at(l - 1) == QLatin1Char(')')); + + QStringList result; + QString item; + + for (++idx; idx < l; ++idx) { + QChar c = s.at(idx); + if (c == QLatin1Char(')')) { + Q_ASSERT(idx == l - 1); + result.append(item); + } else if (c == QLatin1Char(' ')) { + result.append(item); + item.clear(); + } else { + item.append(c); + } + } + + return result; +} + +// ************************************************************************ +// QConfFileSettingsPrivate + +/* + If we don't have the permission to read the file, returns false. + If the file doesn't exist, returns true. +*/ +static bool checkAccess(const QString &name) +{ + QFileInfo fileInfo(name); + + if (fileInfo.exists()) { + QFile file(name); + // if the file exists but we can't open it, report an error + return file.open(QFile::ReadOnly); + } else { + return true; + } +} + +void QConfFileSettingsPrivate::initFormat() +{ + extension = (format == QSettings::NativeFormat) ? QLatin1String(".conf") : QLatin1String(".ini"); + readFunc = 0; + writeFunc = 0; +#if defined(Q_OS_MAC) + caseSensitivity = (format == QSettings::NativeFormat) ? Qt::CaseSensitive : IniCaseSensitivity; +#else + caseSensitivity = IniCaseSensitivity; +#endif + + if (format > QSettings::IniFormat) { + QMutexLocker locker(globalMutex()); + const CustomFormatVector *customFormatVector = customFormatVectorFunc(); + + int i = (int)format - (int)QSettings::CustomFormat1; + if (i >= 0 && i < customFormatVector->size()) { + QConfFileCustomFormat info = customFormatVector->at(i); + extension = info.extension; + readFunc = info.readFunc; + writeFunc = info.writeFunc; + caseSensitivity = info.caseSensitivity; + } + } +} + +void QConfFileSettingsPrivate::initAccess() +{ + bool readAccess = false; + if (confFiles[spec]) { + readAccess = checkAccess(confFiles[spec]->name); + if (format > QSettings::IniFormat) { + if (!readFunc) + readAccess = false; + } + } + + if (!readAccess) + setStatus(QSettings::AccessError); + + sync(); // loads the files the first time +} + +#ifdef Q_OS_WIN +static QString windowsConfigPath(int type) +{ + QString result; + +#ifndef QT_NO_QOBJECT + // We can't use QLibrary if there is QT_NO_QOBJECT is defined + // This only happens when bootstrapping qmake. +#ifndef Q_OS_WINCE + QSystemLibrary library(QLatin1String("shell32")); +#else + QSystemLibrary library(QLatin1String("coredll")); +#endif // Q_OS_WINCE + typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPWSTR, int, BOOL); + GetSpecialFolderPath SHGetSpecialFolderPath = (GetSpecialFolderPath)library.resolve("SHGetSpecialFolderPathW"); + if (SHGetSpecialFolderPath) { + wchar_t path[MAX_PATH]; + SHGetSpecialFolderPath(0, path, type, FALSE); + result = QString::fromWCharArray(path); + } + +#endif // QT_NO_QOBJECT + + if (result.isEmpty()) { + switch (type) { +#ifndef Q_OS_WINCE + case CSIDL_COMMON_APPDATA: + result = QLatin1String("C:\\temp\\qt-common"); + break; + case CSIDL_APPDATA: + result = QLatin1String("C:\\temp\\qt-user"); + break; +#else + case CSIDL_COMMON_APPDATA: + result = QLatin1String("\\Temp\\qt-common"); + break; + case CSIDL_APPDATA: + result = QLatin1String("\\Temp\\qt-user"); + break; +#endif + default: + ; + } + } + + return result; +} +#endif // Q_OS_WIN + +static inline int pathHashKey(QSettings::Format format, QSettings::Scope scope) +{ + return int((uint(format) << 1) | uint(scope == QSettings::SystemScope)); +} + +static void initDefaultPaths(QMutexLocker *locker) +{ + PathHash *pathHash = pathHashFunc(); + QString homePath = QDir::homePath(); + QString systemPath; + + locker->unlock(); + + /* + QLibraryInfo::location() uses QSettings, so in order to + avoid a dead-lock, we can't hold the global mutex while + calling it. + */ + systemPath = QLibraryInfo::location(QLibraryInfo::SettingsPath); + systemPath += QLatin1Char('/'); + + locker->relock(); + if (pathHash->isEmpty()) { + /* + Lazy initialization of pathHash. We initialize the + IniFormat paths and (on Unix) the NativeFormat paths. + (The NativeFormat paths are not configurable for the + Windows registry and the Mac CFPreferences.) + */ +#ifdef Q_OS_WIN + pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::UserScope), + windowsConfigPath(CSIDL_APPDATA) + QDir::separator()); + pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::SystemScope), + windowsConfigPath(CSIDL_COMMON_APPDATA) + QDir::separator()); +#else + QString userPath; + char *env = getenv("XDG_CONFIG_HOME"); + if (env == 0) { + userPath = homePath; + userPath += QLatin1Char('/'); +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) + userPath += QLatin1String("Settings"); +#else + userPath += QLatin1String(".config"); +#endif + } else if (*env == '/') { + userPath = QLatin1String(env); + } else { + userPath = homePath; + userPath += QLatin1Char('/'); + userPath += QLatin1String(env); + } + userPath += QLatin1Char('/'); + + pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::UserScope), userPath); + pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::SystemScope), systemPath); +#ifndef Q_OS_MAC + pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::UserScope), userPath); + pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::SystemScope), systemPath); +#endif +#endif + } +} + +static QString getPath(QSettings::Format format, QSettings::Scope scope) +{ + Q_ASSERT((int)QSettings::NativeFormat == 0); + Q_ASSERT((int)QSettings::IniFormat == 1); + + QMutexLocker locker(globalMutex()); + PathHash *pathHash = pathHashFunc(); + if (pathHash->isEmpty()) + initDefaultPaths(&locker); + + QString result = pathHash->value(pathHashKey(format, scope)); + if (!result.isEmpty()) + return result; + + // fall back on INI path + return pathHash->value(pathHashKey(QSettings::IniFormat, scope)); +} + +QConfFileSettingsPrivate::QConfFileSettingsPrivate(QSettings::Format format, + QSettings::Scope scope, + const QString &organization, + const QString &application) + : QSettingsPrivate(format, scope, organization, application), + nextPosition(0x40000000) // big positive number +{ + int i; + initFormat(); + + QString org = organization; + if (org.isEmpty()) { + setStatus(QSettings::AccessError); + org = QLatin1String("Unknown Organization"); + } + + QString appFile = org + QDir::separator() + application + extension; + QString orgFile = org + extension; + + if (scope == QSettings::UserScope) { + QString userPath = getPath(format, QSettings::UserScope); + if (!application.isEmpty()) + confFiles[F_User | F_Application].reset(QConfFile::fromName(userPath + appFile, true)); + confFiles[F_User | F_Organization].reset(QConfFile::fromName(userPath + orgFile, true)); + } + + QString systemPath = getPath(format, QSettings::SystemScope); + if (!application.isEmpty()) + confFiles[F_System | F_Application].reset(QConfFile::fromName(systemPath + appFile, false)); + confFiles[F_System | F_Organization].reset(QConfFile::fromName(systemPath + orgFile, false)); + + for (i = 0; i < NumConfFiles; ++i) { + if (confFiles[i]) { + spec = i; + break; + } + } + + initAccess(); +} + +QConfFileSettingsPrivate::QConfFileSettingsPrivate(const QString &fileName, + QSettings::Format format) + : QSettingsPrivate(format), + nextPosition(0x40000000) // big positive number +{ + initFormat(); + + confFiles[0].reset(QConfFile::fromName(fileName, true)); + + initAccess(); +} + +QConfFileSettingsPrivate::~QConfFileSettingsPrivate() +{ + QMutexLocker locker(globalMutex()); + ConfFileHash *usedHash = usedHashFunc(); + ConfFileCache *unusedCache = unusedCacheFunc(); + + for (int i = 0; i < NumConfFiles; ++i) { + if (confFiles[i] && !confFiles[i]->ref.deref()) { + if (confFiles[i]->size == 0) { + delete confFiles[i].take(); + } else { + if (usedHash) + usedHash->remove(confFiles[i]->name); + if (unusedCache) { + QT_TRY { + // compute a better size? + unusedCache->insert(confFiles[i]->name, confFiles[i].data(), + 10 + (confFiles[i]->originalKeys.size() / 4)); + confFiles[i].take(); + } QT_CATCH(...) { + // out of memory. Do not cache the file. + delete confFiles[i].take(); + } + } else { + // unusedCache is gone - delete the entry to prevent a memory leak + delete confFiles[i].take(); + } + } + } + // prevent the ScopedPointer to deref it again. + confFiles[i].take(); + } +} + +void QConfFileSettingsPrivate::remove(const QString &key) +{ + QConfFile *confFile = confFiles[spec].data(); + if (!confFile) + return; + + QSettingsKey theKey(key, caseSensitivity); + QSettingsKey prefix(key + QLatin1Char('/'), caseSensitivity); + QMutexLocker locker(&confFile->mutex); + + ensureSectionParsed(confFile, theKey); + ensureSectionParsed(confFile, prefix); + + ParsedSettingsMap::iterator i = confFile->addedKeys.lowerBound(prefix); + while (i != confFile->addedKeys.end() && i.key().startsWith(prefix)) + i = confFile->addedKeys.erase(i); + confFile->addedKeys.remove(theKey); + + ParsedSettingsMap::const_iterator j = const_cast<const ParsedSettingsMap *>(&confFile->originalKeys)->lowerBound(prefix); + while (j != confFile->originalKeys.constEnd() && j.key().startsWith(prefix)) { + confFile->removedKeys.insert(j.key(), QVariant()); + ++j; + } + if (confFile->originalKeys.contains(theKey)) + confFile->removedKeys.insert(theKey, QVariant()); +} + +void QConfFileSettingsPrivate::set(const QString &key, const QVariant &value) +{ + QConfFile *confFile = confFiles[spec].data(); + if (!confFile) + return; + + QSettingsKey theKey(key, caseSensitivity, nextPosition++); + QMutexLocker locker(&confFile->mutex); + confFile->removedKeys.remove(theKey); + confFile->addedKeys.insert(theKey, value); +} + +bool QConfFileSettingsPrivate::get(const QString &key, QVariant *value) const +{ + QSettingsKey theKey(key, caseSensitivity); + ParsedSettingsMap::const_iterator j; + bool found = false; + + for (int i = 0; i < NumConfFiles; ++i) { + if (QConfFile *confFile = confFiles[i].data()) { + QMutexLocker locker(&confFile->mutex); + + if (!confFile->addedKeys.isEmpty()) { + j = confFile->addedKeys.constFind(theKey); + found = (j != confFile->addedKeys.constEnd()); + } + if (!found) { + ensureSectionParsed(confFile, theKey); + j = confFile->originalKeys.constFind(theKey); + found = (j != confFile->originalKeys.constEnd() + && !confFile->removedKeys.contains(theKey)); + } + + if (found && value) + *value = *j; + + if (found) + return true; + if (!fallbacks) + break; + } + } + return false; +} + +QStringList QConfFileSettingsPrivate::children(const QString &prefix, ChildSpec spec) const +{ + QMap<QString, QString> result; + ParsedSettingsMap::const_iterator j; + + QSettingsKey thePrefix(prefix, caseSensitivity); + int startPos = prefix.size(); + + for (int i = 0; i < NumConfFiles; ++i) { + if (QConfFile *confFile = confFiles[i].data()) { + QMutexLocker locker(&confFile->mutex); + + if (thePrefix.isEmpty()) { + ensureAllSectionsParsed(confFile); + } else { + ensureSectionParsed(confFile, thePrefix); + } + + j = const_cast<const ParsedSettingsMap *>( + &confFile->originalKeys)->lowerBound( thePrefix); + while (j != confFile->originalKeys.constEnd() && j.key().startsWith(thePrefix)) { + if (!confFile->removedKeys.contains(j.key())) + processChild(j.key().originalCaseKey().mid(startPos), spec, result); + ++j; + } + + j = const_cast<const ParsedSettingsMap *>( + &confFile->addedKeys)->lowerBound(thePrefix); + while (j != confFile->addedKeys.constEnd() && j.key().startsWith(thePrefix)) { + processChild(j.key().originalCaseKey().mid(startPos), spec, result); + ++j; + } + + if (!fallbacks) + break; + } + } + return result.keys(); +} + +void QConfFileSettingsPrivate::clear() +{ + QConfFile *confFile = confFiles[spec].data(); + if (!confFile) + return; + + QMutexLocker locker(&confFile->mutex); + ensureAllSectionsParsed(confFile); + confFile->addedKeys.clear(); + confFile->removedKeys = confFile->originalKeys; +} + +void QConfFileSettingsPrivate::sync() +{ + // people probably won't be checking the status a whole lot, so in case of + // error we just try to go on and make the best of it + + for (int i = 0; i < NumConfFiles; ++i) { + QConfFile *confFile = confFiles[i].data(); + if (confFile) { + QMutexLocker locker(&confFile->mutex); + syncConfFile(i); + } + } +} + +void QConfFileSettingsPrivate::flush() +{ + sync(); +} + +QString QConfFileSettingsPrivate::fileName() const +{ + QConfFile *confFile = confFiles[spec].data(); + if (!confFile) + return QString(); + return confFile->name; +} + +bool QConfFileSettingsPrivate::isWritable() const +{ + if (format > QSettings::IniFormat && !writeFunc) + return false; + + QConfFile *confFile = confFiles[spec].data(); + if (!confFile) + return false; + + return confFile->isWritable(); +} + +void QConfFileSettingsPrivate::syncConfFile(int confFileNo) +{ + QConfFile *confFile = confFiles[confFileNo].data(); + bool readOnly = confFile->addedKeys.isEmpty() && confFile->removedKeys.isEmpty(); + bool ok; + + /* + We can often optimize the read-only case, if the file on disk + hasn't changed. + */ + if (readOnly) { + QFileInfo fileInfo(confFile->name); + if (confFile->size == fileInfo.size() && confFile->timeStamp == fileInfo.lastModified()) + return; + } + + /* + Open the configuration file and try to use it using a named + semaphore on Windows and an advisory lock on Unix-based + systems. This protect us against other QSettings instances + trying to access the same file from other threads or + processes. + + As it stands now, the locking mechanism doesn't work for + .plist files. + */ + QFile file(confFile->name); + bool createFile = !file.exists(); + if (!readOnly && confFile->isWritable()) + file.open(QFile::ReadWrite); + if (!file.isOpen()) + file.open(QFile::ReadOnly); + +#ifdef Q_OS_WIN + HANDLE readSemaphore = 0; + HANDLE writeSemaphore = 0; + static const int FileLockSemMax = 50; + int numReadLocks = readOnly ? 1 : FileLockSemMax; + + if (file.isOpen()) { + // Acquire the write lock if we will be writing + if (!readOnly) { + QString writeSemName = QLatin1String("QSettingsWriteSem "); + writeSemName.append(file.fileName()); + + writeSemaphore = CreateSemaphore(0, 1, 1, reinterpret_cast<const wchar_t *>(writeSemName.utf16())); + + if (writeSemaphore) { + WaitForSingleObject(writeSemaphore, INFINITE); + } else { + setStatus(QSettings::AccessError); + return; + } + } + + // Acquire all the read locks if we will be writing, to make sure nobody + // reads while we're writing. If we are only reading, acquire a single + // read lock. + QString readSemName(QLatin1String("QSettingsReadSem ")); + readSemName.append(file.fileName()); + + readSemaphore = CreateSemaphore(0, FileLockSemMax, FileLockSemMax, reinterpret_cast<const wchar_t *>(readSemName.utf16())); + + if (readSemaphore) { + for (int i = 0; i < numReadLocks; ++i) + WaitForSingleObject(readSemaphore, INFINITE); + } else { + setStatus(QSettings::AccessError); + if (writeSemaphore != 0) { + ReleaseSemaphore(writeSemaphore, 1, 0); + CloseHandle(writeSemaphore); + } + return; + } + } +#else + if (file.isOpen()) + unixLock(file.handle(), readOnly ? F_RDLCK : F_WRLCK); +#endif + + // If we have created the file, apply the file perms + if (file.isOpen()) { + if (createFile) { + QFile::Permissions perms = file.permissions() | QFile::ReadOwner | QFile::WriteOwner; + if (!confFile->userPerms) + perms |= QFile::ReadGroup | QFile::ReadOther; + file.setPermissions(perms); + } + } + + /* + We hold the lock. Let's reread the file if it has changed + since last time we read it. + */ + QFileInfo fileInfo(confFile->name); + bool mustReadFile = true; + + if (!readOnly) + mustReadFile = (confFile->size != fileInfo.size() + || (confFile->size != 0 && confFile->timeStamp != fileInfo.lastModified())); + + if (mustReadFile) { + confFile->unparsedIniSections.clear(); + confFile->originalKeys.clear(); + + /* + Files that we can't read (because of permissions or + because they don't exist) are treated as empty files. + */ + if (file.isReadable() && fileInfo.size() != 0) { +#ifdef Q_OS_MAC + if (format == QSettings::NativeFormat) { + ok = readPlistFile(confFile->name, &confFile->originalKeys); + } else +#endif + { + if (format <= QSettings::IniFormat) { + QByteArray data = file.readAll(); + ok = readIniFile(data, &confFile->unparsedIniSections); + } else { + if (readFunc) { + QSettings::SettingsMap tempNewKeys; + ok = readFunc(file, tempNewKeys); + + if (ok) { + QSettings::SettingsMap::const_iterator i = tempNewKeys.constBegin(); + while (i != tempNewKeys.constEnd()) { + confFile->originalKeys.insert(QSettingsKey(i.key(), + caseSensitivity), + i.value()); + ++i; + } + } + } else { + ok = false; + } + } + } + + if (!ok) + setStatus(QSettings::FormatError); + } + + confFile->size = fileInfo.size(); + confFile->timeStamp = fileInfo.lastModified(); + } + + /* + We also need to save the file. We still hold the file lock, + so everything is under control. + */ + if (!readOnly) { + ensureAllSectionsParsed(confFile); + ParsedSettingsMap mergedKeys = confFile->mergedKeyMap(); + + if (file.isWritable()) { +#ifdef Q_OS_MAC + if (format == QSettings::NativeFormat) { + ok = writePlistFile(confFile->name, mergedKeys); + } else +#endif + { + file.seek(0); + file.resize(0); + + if (format <= QSettings::IniFormat) { + ok = writeIniFile(file, mergedKeys); + if (!ok) { + // try to restore old data; might work if the disk was full and the new data + // was larger than the old data + file.seek(0); + file.resize(0); + writeIniFile(file, confFile->originalKeys); + } + } else { + if (writeFunc) { + QSettings::SettingsMap tempOriginalKeys; + + ParsedSettingsMap::const_iterator i = mergedKeys.constBegin(); + while (i != mergedKeys.constEnd()) { + tempOriginalKeys.insert(i.key(), i.value()); + ++i; + } + ok = writeFunc(file, tempOriginalKeys); + } else { + ok = false; + } + } + } + } else { + ok = false; + } + + if (ok) { + confFile->unparsedIniSections.clear(); + confFile->originalKeys = mergedKeys; + confFile->addedKeys.clear(); + confFile->removedKeys.clear(); + + QFileInfo fileInfo(confFile->name); + confFile->size = fileInfo.size(); + confFile->timeStamp = fileInfo.lastModified(); + } else { + setStatus(QSettings::AccessError); + } + } + + /* + Release the file lock. + */ +#ifdef Q_OS_WIN + if (readSemaphore != 0) { + ReleaseSemaphore(readSemaphore, numReadLocks, 0); + CloseHandle(readSemaphore); + } + if (writeSemaphore != 0) { + ReleaseSemaphore(writeSemaphore, 1, 0); + CloseHandle(writeSemaphore); + } +#endif +} + +enum { Space = 0x1, Special = 0x2 }; + +static const char charTraits[256] = +{ + // Space: '\t', '\n', '\r', ' ' + // Special: '\n', '\r', '"', ';', '=', '\\' + + 0, 0, 0, 0, 0, 0, 0, 0, 0, Space, Space | Special, 0, 0, Space | Special, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Space, 0, Special, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Special, 0, Special, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Special, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +bool QConfFileSettingsPrivate::readIniLine(const QByteArray &data, int &dataPos, + int &lineStart, int &lineLen, int &equalsPos) +{ + int dataLen = data.length(); + bool inQuotes = false; + + equalsPos = -1; + + lineStart = dataPos; + while (lineStart < dataLen && (charTraits[uint(uchar(data.at(lineStart)))] & Space)) + ++lineStart; + + int i = lineStart; + while (i < dataLen) { + while (!(charTraits[uint(uchar(data.at(i)))] & Special)) { + if (++i == dataLen) + goto break_out_of_outer_loop; + } + + char ch = data.at(i++); + if (ch == '=') { + if (!inQuotes && equalsPos == -1) + equalsPos = i - 1; + } else if (ch == '\n' || ch == '\r') { + if (i == lineStart + 1) { + ++lineStart; + } else if (!inQuotes) { + --i; + goto break_out_of_outer_loop; + } + } else if (ch == '\\') { + if (i < dataLen) { + char ch = data.at(i++); + if (i < dataLen) { + char ch2 = data.at(i); + // \n, \r, \r\n, and \n\r are legitimate line terminators in INI files + if ((ch == '\n' && ch2 == '\r') || (ch == '\r' && ch2 == '\n')) + ++i; + } + } + } else if (ch == '"') { + inQuotes = !inQuotes; + } else { + Q_ASSERT(ch == ';'); + + if (i == lineStart + 1) { + char ch; + while (i < dataLen && ((ch = data.at(i) != '\n') && ch != '\r')) + ++i; + lineStart = i; + } else if (!inQuotes) { + --i; + goto break_out_of_outer_loop; + } + } + } + +break_out_of_outer_loop: + dataPos = i; + lineLen = i - lineStart; + return lineLen > 0; +} + +/* + Returns false on parse error. However, as many keys are read as + possible, so if the user doesn't check the status he will get the + most out of the file anyway. +*/ +bool QConfFileSettingsPrivate::readIniFile(const QByteArray &data, + UnparsedSettingsMap *unparsedIniSections) +{ +#define FLUSH_CURRENT_SECTION() \ + { \ + QByteArray §ionData = (*unparsedIniSections)[QSettingsKey(currentSection, \ + IniCaseSensitivity, \ + sectionPosition)]; \ + if (!sectionData.isEmpty()) \ + sectionData.append('\n'); \ + sectionData += data.mid(currentSectionStart, lineStart - currentSectionStart); \ + sectionPosition = ++position; \ + } + + QString currentSection; + int currentSectionStart = 0; + int dataPos = 0; + int lineStart; + int lineLen; + int equalsPos; + int position = 0; + int sectionPosition = 0; + bool ok = true; + + while (readIniLine(data, dataPos, lineStart, lineLen, equalsPos)) { + char ch = data.at(lineStart); + if (ch == '[') { + FLUSH_CURRENT_SECTION(); + + // this is a section + QByteArray iniSection; + int idx = data.indexOf(']', lineStart); + if (idx == -1 || idx >= lineStart + lineLen) { + ok = false; + iniSection = data.mid(lineStart + 1, lineLen - 1); + } else { + iniSection = data.mid(lineStart + 1, idx - lineStart - 1); + } + + iniSection = iniSection.trimmed(); + + if (qstricmp(iniSection, "general") == 0) { + currentSection.clear(); + } else { + if (qstricmp(iniSection, "%general") == 0) { + currentSection = QLatin1String(iniSection.constData() + 1); + } else { + currentSection.clear(); + iniUnescapedKey(iniSection, 0, iniSection.size(), currentSection); + } + currentSection += QLatin1Char('/'); + } + currentSectionStart = dataPos; + } + ++position; + } + + Q_ASSERT(lineStart == data.length()); + FLUSH_CURRENT_SECTION(); + + return ok; + +#undef FLUSH_CURRENT_SECTION +} + +bool QConfFileSettingsPrivate::readIniSection(const QSettingsKey §ion, const QByteArray &data, + ParsedSettingsMap *settingsMap, QTextCodec *codec) +{ + QStringList strListValue; + bool sectionIsLowercase = (section == section.originalCaseKey()); + int equalsPos; + + bool ok = true; + int dataPos = 0; + int lineStart; + int lineLen; + int position = section.originalKeyPosition(); + + while (readIniLine(data, dataPos, lineStart, lineLen, equalsPos)) { + char ch = data.at(lineStart); + Q_ASSERT(ch != '['); + + if (equalsPos == -1) { + if (ch != ';') + ok = false; + continue; + } + + int keyEnd = equalsPos; + while (keyEnd > lineStart && ((ch = data.at(keyEnd - 1)) == ' ' || ch == '\t')) + --keyEnd; + int valueStart = equalsPos + 1; + + QString key = section.originalCaseKey(); + bool keyIsLowercase = (iniUnescapedKey(data, lineStart, keyEnd, key) && sectionIsLowercase); + + QString strValue; + strValue.reserve(lineLen - (valueStart - lineStart)); + bool isStringList = iniUnescapedStringList(data, valueStart, lineStart + lineLen, + strValue, strListValue, codec); + QVariant variant; + if (isStringList) { + variant = stringListToVariantList(strListValue); + } else { + variant = stringToVariant(strValue); + } + + /* + We try to avoid the expensive toLower() call in + QSettingsKey by passing Qt::CaseSensitive when the + key is already in lowercase. + */ + settingsMap->insert(QSettingsKey(key, keyIsLowercase ? Qt::CaseSensitive + : IniCaseSensitivity, + position), + variant); + ++position; + } + + return ok; +} + +class QSettingsIniKey : public QString +{ +public: + inline QSettingsIniKey() : position(-1) {} + inline QSettingsIniKey(const QString &str, int pos = -1) : QString(str), position(pos) {} + + int position; +}; + +static bool operator<(const QSettingsIniKey &k1, const QSettingsIniKey &k2) +{ + if (k1.position != k2.position) + return k1.position < k2.position; + return static_cast<const QString &>(k1) < static_cast<const QString &>(k2); +} + +typedef QMap<QSettingsIniKey, QVariant> IniKeyMap; + +struct QSettingsIniSection +{ + int position; + IniKeyMap keyMap; + + inline QSettingsIniSection() : position(-1) {} +}; + +typedef QMap<QString, QSettingsIniSection> IniMap; + +/* + This would be more straightforward if we didn't try to remember the original + key order in the .ini file, but we do. +*/ +bool QConfFileSettingsPrivate::writeIniFile(QIODevice &device, const ParsedSettingsMap &map) +{ + IniMap iniMap; + IniMap::const_iterator i; + +#ifdef Q_OS_WIN + const char * const eol = "\r\n"; +#else + const char eol = '\n'; +#endif + + for (ParsedSettingsMap::const_iterator j = map.constBegin(); j != map.constEnd(); ++j) { + QString section; + QSettingsIniKey key(j.key().originalCaseKey(), j.key().originalKeyPosition()); + int slashPos; + + if ((slashPos = key.indexOf(QLatin1Char('/'))) != -1) { + section = key.left(slashPos); + key.remove(0, slashPos + 1); + } + + QSettingsIniSection &iniSection = iniMap[section]; + + // -1 means infinity + if (uint(key.position) < uint(iniSection.position)) + iniSection.position = key.position; + iniSection.keyMap[key] = j.value(); + } + + const int sectionCount = iniMap.size(); + QVector<QSettingsIniKey> sections; + sections.reserve(sectionCount); + for (i = iniMap.constBegin(); i != iniMap.constEnd(); ++i) + sections.append(QSettingsIniKey(i.key(), i.value().position)); + qSort(sections); + + bool writeError = false; + for (int j = 0; !writeError && j < sectionCount; ++j) { + i = iniMap.constFind(sections.at(j)); + Q_ASSERT(i != iniMap.constEnd()); + + QByteArray realSection; + + iniEscapedKey(i.key(), realSection); + + if (realSection.isEmpty()) { + realSection = "[General]"; + } else if (qstricmp(realSection, "general") == 0) { + realSection = "[%General]"; + } else { + realSection.prepend('['); + realSection.append(']'); + } + + if (j != 0) + realSection.prepend(eol); + realSection += eol; + + device.write(realSection); + + const IniKeyMap &ents = i.value().keyMap; + for (IniKeyMap::const_iterator j = ents.constBegin(); j != ents.constEnd(); ++j) { + QByteArray block; + iniEscapedKey(j.key(), block); + block += '='; + + const QVariant &value = j.value(); + + /* + The size() != 1 trick is necessary because + QVariant(QString("foo")).toList() returns an empty + list, not a list containing "foo". + */ + if (value.type() == QVariant::StringList + || (value.type() == QVariant::List && value.toList().size() != 1)) { + iniEscapedStringList(variantListToStringList(value.toList()), block, iniCodec); + } else { + iniEscapedString(variantToString(value), block, iniCodec); + } + block += eol; + if (device.write(block) == -1) { + writeError = true; + break; + } + } + } + return !writeError; +} + +void QConfFileSettingsPrivate::ensureAllSectionsParsed(QConfFile *confFile) const +{ + UnparsedSettingsMap::const_iterator i = confFile->unparsedIniSections.constBegin(); + const UnparsedSettingsMap::const_iterator end = confFile->unparsedIniSections.constEnd(); + + for (; i != end; ++i) { + if (!QConfFileSettingsPrivate::readIniSection(i.key(), i.value(), &confFile->originalKeys, iniCodec)) + setStatus(QSettings::FormatError); + } + confFile->unparsedIniSections.clear(); +} + +void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile, + const QSettingsKey &key) const +{ + if (confFile->unparsedIniSections.isEmpty()) + return; + + UnparsedSettingsMap::iterator i; + + int indexOfSlash = key.indexOf(QLatin1Char('/')); + if (indexOfSlash != -1) { + i = confFile->unparsedIniSections.upperBound(key); + if (i == confFile->unparsedIniSections.begin()) + return; + --i; + if (i.key().isEmpty() || !key.startsWith(i.key())) + return; + } else { + i = confFile->unparsedIniSections.begin(); + if (i == confFile->unparsedIniSections.end() || !i.key().isEmpty()) + return; + } + + if (!QConfFileSettingsPrivate::readIniSection(i.key(), i.value(), &confFile->originalKeys, iniCodec)) + setStatus(QSettings::FormatError); + confFile->unparsedIniSections.erase(i); +} + +/*! + \class QSettings + \brief The QSettings class provides persistent platform-independent application settings. + + \ingroup io + + \reentrant + + Users normally expect an application to remember its settings + (window sizes and positions, options, etc.) across sessions. This + information is often stored in the system registry on Windows, + and in XML preferences files on Mac OS X. On Unix systems, in the + absence of a standard, many applications (including the KDE + applications) use INI text files. + + QSettings is an abstraction around these technologies, enabling + you to save and restore application settings in a portable + manner. It also supports \l{registerFormat()}{custom storage + formats}. + + QSettings's API is based on QVariant, allowing you to save + most value-based types, such as QString, QRect, and QImage, + with the minimum of effort. + + If all you need is a non-persistent memory-based structure, + consider using QMap<QString, QVariant> instead. + + \tableofcontents section1 + + \section1 Basic Usage + + When creating a QSettings object, you must pass the name of your + company or organization as well as the name of your application. + For example, if your product is called Star Runner and your + company is called MySoft, you would construct the QSettings + object as follows: + + \snippet doc/src/snippets/settings/settings.cpp 0 + + QSettings objects can be created either on the stack or on + the heap (i.e. using \c new). Constructing and destroying a + QSettings object is very fast. + + If you use QSettings from many places in your application, you + might want to specify the organization name and the application + name using QCoreApplication::setOrganizationName() and + QCoreApplication::setApplicationName(), and then use the default + QSettings constructor: + + \snippet doc/src/snippets/settings/settings.cpp 1 + \snippet doc/src/snippets/settings/settings.cpp 2 + \snippet doc/src/snippets/settings/settings.cpp 3 + \dots + \snippet doc/src/snippets/settings/settings.cpp 4 + + (Here, we also specify the organization's Internet domain. When + the Internet domain is set, it is used on Mac OS X instead of the + organization name, since Mac OS X applications conventionally use + Internet domains to identify themselves. If no domain is set, a + fake domain is derived from the organization name. See the + \l{Platform-Specific Notes} below for details.) + + QSettings stores settings. Each setting consists of a QString + that specifies the setting's name (the \e key) and a QVariant + that stores the data associated with the key. To write a setting, + use setValue(). For example: + + \snippet doc/src/snippets/settings/settings.cpp 5 + + If there already exists a setting with the same key, the existing + value is overwritten by the new value. For efficiency, the + changes may not be saved to permanent storage immediately. (You + can always call sync() to commit your changes.) + + You can get a setting's value back using value(): + + \snippet doc/src/snippets/settings/settings.cpp 6 + + If there is no setting with the specified name, QSettings + returns a null QVariant (which can be converted to the integer 0). + You can specify another default value by passing a second + argument to value(): + + \snippet doc/src/snippets/settings/settings.cpp 7 + + To test whether a given key exists, call contains(). To remove + the setting associated with a key, call remove(). To obtain the + list of all keys, call allKeys(). To remove all keys, call + clear(). + + \section1 QVariant and GUI Types + + Because QVariant is part of the \l QtCore library, it cannot provide + conversion functions to data types such as QColor, QImage, and + QPixmap, which are part of \l QtGui. In other words, there is no + \c toColor(), \c toImage(), or \c toPixmap() functions in QVariant. + + Instead, you can use the QVariant::value() or the qVariantValue() + template function. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 0 + + The inverse conversion (e.g., from QColor to QVariant) is + automatic for all data types supported by QVariant, including + GUI-related types: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 1 + + Custom types registered using qRegisterMetaType() and + qRegisterMetaTypeStreamOperators() can be stored using QSettings. + + \section1 Section and Key Syntax + + Setting keys can contain any Unicode characters. The Windows + registry and INI files use case-insensitive keys, whereas the + Carbon Preferences API on Mac OS X uses case-sensitive keys. To + avoid portability problems, follow these simple rules: + + \list 1 + \o Always refer to the same key using the same case. For example, + if you refer to a key as "text fonts" in one place in your + code, don't refer to it as "Text Fonts" somewhere else. + + \o Avoid key names that are identical except for the case. For + example, if you have a key called "MainWindow", don't try to + save another key as "mainwindow". + + \o Do not use slashes ('/' and '\\') in section or key names; the + backslash character is used to separate sub keys (see below). On + windows '\\' are converted by QSettings to '/', which makes + them identical. + \endlist + + You can form hierarchical keys using the '/' character as a + separator, similar to Unix file paths. For example: + + \snippet doc/src/snippets/settings/settings.cpp 8 + \snippet doc/src/snippets/settings/settings.cpp 9 + \snippet doc/src/snippets/settings/settings.cpp 10 + + If you want to save or restore many settings with the same + prefix, you can specify the prefix using beginGroup() and call + endGroup() at the end. Here's the same example again, but this + time using the group mechanism: + + \snippet doc/src/snippets/settings/settings.cpp 11 + \codeline + \snippet doc/src/snippets/settings/settings.cpp 12 + + If a group is set using beginGroup(), the behavior of most + functions changes consequently. Groups can be set recursively. + + In addition to groups, QSettings also supports an "array" + concept. See beginReadArray() and beginWriteArray() for details. + + \section1 Fallback Mechanism + + Let's assume that you have created a QSettings object with the + organization name MySoft and the application name Star Runner. + When you look up a value, up to four locations are searched in + that order: + + \list 1 + \o a user-specific location for the Star Runner application + \o a user-specific location for all applications by MySoft + \o a system-wide location for the Star Runner application + \o a system-wide location for all applications by MySoft + \endlist + + (See \l{Platform-Specific Notes} below for information on what + these locations are on the different platforms supported by Qt.) + + If a key cannot be found in the first location, the search goes + on in the second location, and so on. This enables you to store + system-wide or organization-wide settings and to override them on + a per-user or per-application basis. To turn off this mechanism, + call setFallbacksEnabled(false). + + Although keys from all four locations are available for reading, + only the first file (the user-specific location for the + application at hand) is accessible for writing. To write to any + of the other files, omit the application name and/or specify + QSettings::SystemScope (as opposed to QSettings::UserScope, the + default). + + Let's see with an example: + + \snippet doc/src/snippets/settings/settings.cpp 13 + \snippet doc/src/snippets/settings/settings.cpp 14 + + The table below summarizes which QSettings objects access + which location. "\bold{X}" means that the location is the main + location associated to the QSettings object and is used both + for reading and for writing; "o" means that the location is used + as a fallback when reading. + + \table + \header \o Locations \o \c{obj1} \o \c{obj2} \o \c{obj3} \o \c{obj4} + \row \o 1. User, Application \o \bold{X} \o \o \o + \row \o 2. User, Organization \o o \o \bold{X} \o \o + \row \o 3. System, Application \o o \o \o \bold{X} \o + \row \o 4. System, Organization \o o \o o \o o \o \bold{X} + \endtable + + The beauty of this mechanism is that it works on all platforms + supported by Qt and that it still gives you a lot of flexibility, + without requiring you to specify any file names or registry + paths. + + If you want to use INI files on all platforms instead of the + native API, you can pass QSettings::IniFormat as the first + argument to the QSettings constructor, followed by the scope, the + organization name, and the application name: + + \snippet doc/src/snippets/settings/settings.cpp 15 + + The \l{tools/settingseditor}{Settings Editor} example lets you + experiment with different settings location and with fallbacks + turned on or off. + + \section1 Restoring the State of a GUI Application + + QSettings is often used to store the state of a GUI + application. The following example illustrates how to use QSettings + to save and restore the geometry of an application's main window. + + \snippet doc/src/snippets/settings/settings.cpp 16 + \codeline + \snippet doc/src/snippets/settings/settings.cpp 17 + + See \l{Window Geometry} for a discussion on why it is better to + call QWidget::resize() and QWidget::move() rather than QWidget::setGeometry() + to restore a window's geometry. + + The \c readSettings() and \c writeSettings() functions must be + called from the main window's constructor and close event handler + as follows: + + \snippet doc/src/snippets/settings/settings.cpp 18 + \dots + \snippet doc/src/snippets/settings/settings.cpp 19 + \snippet doc/src/snippets/settings/settings.cpp 20 + \codeline + \snippet doc/src/snippets/settings/settings.cpp 21 + + See the \l{mainwindows/application}{Application} example for a + self-contained example that uses QSettings. + + \section1 Accessing Settings from Multiple Threads or Processes Simultaneously + + QSettings is \l{reentrant}. This means that you can use + distinct QSettings object in different threads + simultaneously. This guarantee stands even when the QSettings + objects refer to the same files on disk (or to the same entries + in the system registry). If a setting is modified through one + QSettings object, the change will immediately be visible in + any other QSettings objects that operate on the same location + and that live in the same process. + + QSettings can safely be used from different processes (which can + be different instances of your application running at the same + time or different applications altogether) to read and write to + the same system locations. It uses advisory file locking and a + smart merging algorithm to ensure data integrity. Note that sync() + imports changes made by other processes (in addition to writing + the changes from this QSettings). + + \section1 Platform-Specific Notes + + \section2 Locations Where Application Settings Are Stored + + As mentioned in the \l{Fallback Mechanism} section, QSettings + stores settings for an application in up to four locations, + depending on whether the settings are user-specific or + system-wide and whether the settings are application-specific + or organization-wide. For simplicity, we're assuming the + organization is called MySoft and the application is called Star + Runner. + + On Unix systems, if the file format is NativeFormat, the + following files are used by default: + + \list 1 + \o \c{$HOME/.config/MySoft/Star Runner.conf} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft/Star Runner.conf}) + \o \c{$HOME/.config/MySoft.conf} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft.conf}) + \o \c{/etc/xdg/MySoft/Star Runner.conf} + \o \c{/etc/xdg/MySoft.conf} + \endlist + + On Mac OS X versions 10.2 and 10.3, these files are used by + default: + + \list 1 + \o \c{$HOME/Library/Preferences/com.MySoft.Star Runner.plist} + \o \c{$HOME/Library/Preferences/com.MySoft.plist} + \o \c{/Library/Preferences/com.MySoft.Star Runner.plist} + \o \c{/Library/Preferences/com.MySoft.plist} + \endlist + + On Windows, NativeFormat settings are stored in the following + registry paths: + + \list 1 + \o \c{HKEY_CURRENT_USER\Software\MySoft\Star Runner} + \o \c{HKEY_CURRENT_USER\Software\MySoft} + \o \c{HKEY_LOCAL_MACHINE\Software\MySoft\Star Runner} + \o \c{HKEY_LOCAL_MACHINE\Software\MySoft} + \endlist + + \note On Windows, for 32-bit programs running in WOW64 mode, settings are + stored in the following registry path: + \c{HKEY_LOCAL_MACHINE\Software\WOW6432node}. + + If the file format is IniFormat, the following files are + used on Unix and Mac OS X: + + \list 1 + \o \c{$HOME/.config/MySoft/Star Runner.ini} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft/Star Runner.ini}) + \o \c{$HOME/.config/MySoft.ini} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft.ini}) + \o \c{/etc/xdg/MySoft/Star Runner.ini} + \o \c{/etc/xdg/MySoft.ini} + \endlist + + On Windows, the following files are used: + + \list 1 + \o \c{%APPDATA%\MySoft\Star Runner.ini} + \o \c{%APPDATA%\MySoft.ini} + \o \c{%COMMON_APPDATA%\MySoft\Star Runner.ini} + \o \c{%COMMON_APPDATA%\MySoft.ini} + \endlist + + The \c %APPDATA% path is usually \tt{C:\\Documents and + Settings\\\e{User Name}\\Application Data}; the \c + %COMMON_APPDATA% path is usually \tt{C:\\Documents and + Settings\\All Users\\Application Data}. + + The paths for the \c .ini and \c .conf files can be changed using + setPath(). On Unix and Mac OS X, the user can override them by by + setting the \c XDG_CONFIG_HOME environment variable; see + setPath() for details. + + \section2 Accessing INI and .plist Files Directly + + Sometimes you do want to access settings stored in a specific + file or registry path. On all platforms, if you want to read an + INI file directly, you can use the QSettings constructor that + takes a file name as first argument and pass QSettings::IniFormat + as second argument. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 2 + + You can then use the QSettings object to read and write settings + in the file. + + On Mac OS X, you can access XML-based \c .plist files by passing + QSettings::NativeFormat as second argument. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 3 + + \section2 Accessing the Windows Registry Directly + + On Windows, QSettings lets you access settings that have been + written with QSettings (or settings in a supported format, e.g., string + data) in the system registry. This is done by constructing a QSettings + object with a path in the registry and QSettings::NativeFormat. + + For example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 4 + + All the registry entries that appear under the specified path can + be read or written through the QSettings object as usual (using + forward slashes instead of backslashes). For example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 5 + + Note that the backslash character is, as mentioned, used by + QSettings to separate subkeys. As a result, you cannot read or + write windows registry entries that contain slashes or + backslashes; you should use a native windows API if you need to do + so. + + \section2 Accessing Common Registry Settings on Windows + + On Windows, it is possible for a key to have both a value and subkeys. + Its default value is accessed by using "Default" or "." in + place of a subkey: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 6 + + On other platforms than Windows, "Default" and "." would be + treated as regular subkeys. + + \section2 Platform Limitations + + While QSettings attempts to smooth over the differences between + the different supported platforms, there are still a few + differences that you should be aware of when porting your + application: + + \list + \o The Windows system registry has the following limitations: A + subkey may not exceed 255 characters, an entry's value may + not exceed 16,383 characters, and all the values of a key may + not exceed 65,535 characters. One way to work around these + limitations is to store the settings using the IniFormat + instead of the NativeFormat. + + \o On Mac OS X, allKeys() will return some extra keys for global + settings that apply to all applications. These keys can be + read using value() but cannot be changed, only shadowed. + Calling setFallbacksEnabled(false) will hide these global + settings. + + \o On Mac OS X, the CFPreferences API used by QSettings expects + Internet domain names rather than organization names. To + provide a uniform API, QSettings derives a fake domain name + from the organization name (unless the organization name + already is a domain name, e.g. OpenOffice.org). The algorithm + appends ".com" to the company name and replaces spaces and + other illegal characters with hyphens. If you want to specify + a different domain name, call + QCoreApplication::setOrganizationDomain(), + QCoreApplication::setOrganizationName(), and + QCoreApplication::setApplicationName() in your \c main() + function and then use the default QSettings constructor. + Another solution is to use preprocessor directives, for + example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 7 + + \o On Unix and Mac OS X systems, the advisory file locking is disabled + if NFS (or AutoFS or CacheFS) is detected to work around a bug in the + NFS fcntl() implementation, which hangs forever if statd or lockd aren't + running. Also, the locking isn't performed when accessing \c .plist + files. + + \endlist + + \sa QVariant, QSessionManager, {Settings Editor Example}, {Application Example} +*/ + +/*! \enum QSettings::Status + + The following status values are possible: + + \value NoError No error occurred. + \value AccessError An access error occurred (e.g. trying to write to a read-only file). + \value FormatError A format error occurred (e.g. loading a malformed INI file). + + \sa status() +*/ + +/*! \enum QSettings::Format + + This enum type specifies the storage format used by QSettings. + + \value NativeFormat Store the settings using the most + appropriate storage format for the platform. + On Windows, this means the system registry; + on Mac OS X, this means the CFPreferences + API; on Unix, this means textual + configuration files in INI format. + \value IniFormat Store the settings in INI files. + \value InvalidFormat Special value returned by registerFormat(). + \omitvalue CustomFormat1 + \omitvalue CustomFormat2 + \omitvalue CustomFormat3 + \omitvalue CustomFormat4 + \omitvalue CustomFormat5 + \omitvalue CustomFormat6 + \omitvalue CustomFormat7 + \omitvalue CustomFormat8 + \omitvalue CustomFormat9 + \omitvalue CustomFormat10 + \omitvalue CustomFormat11 + \omitvalue CustomFormat12 + \omitvalue CustomFormat13 + \omitvalue CustomFormat14 + \omitvalue CustomFormat15 + \omitvalue CustomFormat16 + + On Unix, NativeFormat and IniFormat mean the same thing, except + that the file extension is different (\c .conf for NativeFormat, + \c .ini for IniFormat). + + The INI file format is a Windows file format that Qt supports on + all platforms. In the absence of an INI standard, we try to + follow what Microsoft does, with the following exceptions: + + \list + \o If you store types that QVariant can't convert to QString + (e.g., QPoint, QRect, and QSize), Qt uses an \c{@}-based + syntax to encode the type. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 8 + + To minimize compatibility issues, any \c @ that doesn't + appear at the first position in the value or that isn't + followed by a Qt type (\c Point, \c Rect, \c Size, etc.) is + treated as a normal character. + + \o Although backslash is a special character in INI files, most + Windows applications don't escape backslashes (\c{\}) in file + paths: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 9 + + QSettings always treats backslash as a special character and + provides no API for reading or writing such entries. + + \o The INI file format has severe restrictions on the syntax of + a key. Qt works around this by using \c % as an escape + character in keys. In addition, if you save a top-level + setting (a key with no slashes in it, e.g., "someKey"), it + will appear in the INI file's "General" section. To avoid + overwriting other keys, if you save something using the a key + such as "General/someKey", the key will be located in the + "%General" section, \e not in the "General" section. + + \o Following the philosophy that we should be liberal in what + we accept and conservative in what we generate, QSettings + will accept Latin-1 encoded INI files, but generate pure + ASCII files, where non-ASCII values are encoded using standard + INI escape sequences. To make the INI files more readable (but + potentially less compatible), call setIniCodec(). + \endlist + + \sa registerFormat(), setPath() +*/ + +/*! \enum QSettings::Scope + + This enum specifies whether settings are user-specific or shared + by all users of the same system. + + \value UserScope Store settings in a location specific to the + current user (e.g., in the user's home + directory). + \value SystemScope Store settings in a global location, so that + all users on the same machine access the same + set of settings. + \omitvalue User + \omitvalue Global + + \sa setPath() +*/ + +#ifndef QT_NO_QOBJECT +/*! + Constructs a QSettings object for accessing settings of the + application called \a application from the organization called \a + organization, and with parent \a parent. + + Example: + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 10 + + The scope is set to QSettings::UserScope, and the format is + set to QSettings::NativeFormat (i.e. calling setDefaultFormat() + before calling this constructor has no effect). + + \sa setDefaultFormat(), {Fallback Mechanism} +*/ +QSettings::QSettings(const QString &organization, const QString &application, QObject *parent) + : QObject(*QSettingsPrivate::create(NativeFormat, UserScope, organization, application), + parent) +{ +} + +/*! + Constructs a QSettings object for accessing settings of the + application called \a application from the organization called \a + organization, and with parent \a parent. + + If \a scope is QSettings::UserScope, the QSettings object searches + user-specific settings first, before it searches system-wide + settings as a fallback. If \a scope is QSettings::SystemScope, the + QSettings object ignores user-specific settings and provides + access to system-wide settings. + + The storage format is set to QSettings::NativeFormat (i.e. calling + setDefaultFormat() before calling this constructor has no effect). + + If no application name is given, the QSettings object will + only access the organization-wide \l{Fallback Mechanism}{locations}. + + \sa setDefaultFormat() +*/ +QSettings::QSettings(Scope scope, const QString &organization, const QString &application, + QObject *parent) + : QObject(*QSettingsPrivate::create(NativeFormat, scope, organization, application), parent) +{ +} + +/*! + Constructs a QSettings object for accessing settings of the + application called \a application from the organization called + \a organization, and with parent \a parent. + + If \a scope is QSettings::UserScope, the QSettings object searches + user-specific settings first, before it searches system-wide + settings as a fallback. If \a scope is + QSettings::SystemScope, the QSettings object ignores user-specific + settings and provides access to system-wide settings. + + If \a format is QSettings::NativeFormat, the native API is used for + storing settings. If \a format is QSettings::IniFormat, the INI format + is used. + + If no application name is given, the QSettings object will + only access the organization-wide \l{Fallback Mechanism}{locations}. +*/ +QSettings::QSettings(Format format, Scope scope, const QString &organization, + const QString &application, QObject *parent) + : QObject(*QSettingsPrivate::create(format, scope, organization, application), parent) +{ +} + +/*! + Constructs a QSettings object for accessing the settings + stored in the file called \a fileName, with parent \a parent. If + the file doesn't already exist, it is created. + + If \a format is QSettings::NativeFormat, the meaning of \a + fileName depends on the platform. On Unix, \a fileName is the + name of an INI file. On Mac OS X, \a fileName is the name of a + \c .plist file. On Windows, \a fileName is a path in the system + registry. + + If \a format is QSettings::IniFormat, \a fileName is the name of an INI + file. + + \warning This function is provided for convenience. It works well for + accessing INI or \c .plist files generated by Qt, but might fail on some + syntaxes found in such files originated by other programs. In particular, + be aware of the following limitations: + + \list + \o QSettings provides no way of reading INI "path" entries, i.e., entries + with unescaped slash characters. (This is because these entries are + ambiguous and cannot be resolved automatically.) + \o In INI files, QSettings uses the \c @ character as a metacharacter in some + contexts, to encode Qt-specific data types (e.g., \c @Rect), and might + therefore misinterpret it when it occurs in pure INI files. + \endlist + + \sa fileName() +*/ +QSettings::QSettings(const QString &fileName, Format format, QObject *parent) + : QObject(*QSettingsPrivate::create(fileName, format), parent) +{ +} + +/*! + Constructs a QSettings object for accessing settings of the + application and organization set previously with a call to + QCoreApplication::setOrganizationName(), + QCoreApplication::setOrganizationDomain(), and + QCoreApplication::setApplicationName(). + + The scope is QSettings::UserScope and the format is + defaultFormat() (QSettings::NativeFormat by default). + Use setDefaultFormat() before calling this constructor + to change the default format used by this constructor. + + The code + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 11 + + is equivalent to + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 12 + + If QCoreApplication::setOrganizationName() and + QCoreApplication::setApplicationName() has not been previously + called, the QSettings object will not be able to read or write + any settings, and status() will return AccessError. + + On Mac OS X, if both a name and an Internet domain are specified + for the organization, the domain is preferred over the name. On + other platforms, the name is preferred over the domain. + + \sa QCoreApplication::setOrganizationName(), + QCoreApplication::setOrganizationDomain(), + QCoreApplication::setApplicationName(), + setDefaultFormat() +*/ +QSettings::QSettings(QObject *parent) + : QObject(*QSettingsPrivate::create(globalDefaultFormat, UserScope, +#ifdef Q_OS_MAC + QCoreApplication::organizationDomain().isEmpty() + ? QCoreApplication::organizationName() + : QCoreApplication::organizationDomain() +#else + QCoreApplication::organizationName().isEmpty() + ? QCoreApplication::organizationDomain() + : QCoreApplication::organizationName() +#endif + , QCoreApplication::applicationName()), + parent) +{ +} + +#else +QSettings::QSettings(const QString &organization, const QString &application) + : d_ptr(QSettingsPrivate::create(globalDefaultFormat, QSettings::UserScope, organization, application)) +{ + d_ptr->q_ptr = this; +} + +QSettings::QSettings(Scope scope, const QString &organization, const QString &application) + : d_ptr(QSettingsPrivate::create(globalDefaultFormat, scope, organization, application)) +{ + d_ptr->q_ptr = this; +} + +QSettings::QSettings(Format format, Scope scope, const QString &organization, + const QString &application) + : d_ptr(QSettingsPrivate::create(format, scope, organization, application)) +{ + d_ptr->q_ptr = this; +} + +QSettings::QSettings(const QString &fileName, Format format) + : d_ptr(QSettingsPrivate::create(fileName, format)) +{ + d_ptr->q_ptr = this; +} +#endif + +/*! + Destroys the QSettings object. + + Any unsaved changes will eventually be written to permanent + storage. + + \sa sync() +*/ +QSettings::~QSettings() +{ + Q_D(QSettings); + if (d->pendingChanges) { + QT_TRY { + d->flush(); + } QT_CATCH(...) { + ; // ok. then don't flush but at least don't throw in the destructor + } + } +} + +/*! + Removes all entries in the primary location associated to this + QSettings object. + + Entries in fallback locations are not removed. + + If you only want to remove the entries in the current group(), + use remove("") instead. + + \sa remove(), setFallbacksEnabled() +*/ +void QSettings::clear() +{ + Q_D(QSettings); + d->clear(); + d->requestUpdate(); +} + +/*! + Writes any unsaved changes to permanent storage, and reloads any + settings that have been changed in the meantime by another + application. + + This function is called automatically from QSettings's destructor and + by the event loop at regular intervals, so you normally don't need to + call it yourself. + + \sa status() +*/ +void QSettings::sync() +{ + Q_D(QSettings); + d->sync(); +} + +/*! + Returns the path where settings written using this QSettings + object are stored. + + On Windows, if the format is QSettings::NativeFormat, the return value + is a system registry path, not a file path. + + \sa isWritable(), format() +*/ +QString QSettings::fileName() const +{ + Q_D(const QSettings); + return d->fileName(); +} + +/*! + \since 4.4 + + Returns the format used for storing the settings. + + \sa defaultFormat(), fileName(), scope(), organizationName(), applicationName() +*/ +QSettings::Format QSettings::format() const +{ + Q_D(const QSettings); + return d->format; +} + +/*! + \since 4.4 + + Returns the scope used for storing the settings. + + \sa format(), organizationName(), applicationName() +*/ +QSettings::Scope QSettings::scope() const +{ + Q_D(const QSettings); + return d->scope; +} + +/*! + \since 4.4 + + Returns the organization name used for storing the settings. + + \sa QCoreApplication::organizationName(), format(), scope(), applicationName() +*/ +QString QSettings::organizationName() const +{ + Q_D(const QSettings); + return d->organizationName; +} + +/*! + \since 4.4 + + Returns the application name used for storing the settings. + + \sa QCoreApplication::applicationName(), format(), scope(), organizationName() +*/ +QString QSettings::applicationName() const +{ + Q_D(const QSettings); + return d->applicationName; +} + +#ifndef QT_NO_TEXTCODEC + +/*! + \since 4.5 + + Sets the codec for accessing INI files (including \c .conf files on Unix) + to \a codec. The codec is used for decoding any data that is read from + the INI file, and for encoding any data that is written to the file. By + default, no codec is used, and non-ASCII characters are encoded using + standard INI escape sequences. + + \warning The codec must be set immediately after creating the QSettings + object, before accessing any data. + + \sa iniCodec() +*/ +void QSettings::setIniCodec(QTextCodec *codec) +{ + Q_D(QSettings); + d->iniCodec = codec; +} + +/*! + \since 4.5 + \overload + + Sets the codec for accessing INI files (including \c .conf files on Unix) + to the QTextCodec for the encoding specified by \a codecName. Common + values for \c codecName include "ISO 8859-1", "UTF-8", and "UTF-16". + If the encoding isn't recognized, nothing happens. + + \sa QTextCodec::codecForName() +*/ +void QSettings::setIniCodec(const char *codecName) +{ + Q_D(QSettings); + if (QTextCodec *codec = QTextCodec::codecForName(codecName)) + d->iniCodec = codec; +} + +/*! + \since 4.5 + + Returns the codec that is used for accessing INI files. By default, + no codec is used, so a null pointer is returned. +*/ + +QTextCodec *QSettings::iniCodec() const +{ + Q_D(const QSettings); + return d->iniCodec; +} + +#endif // QT_NO_TEXTCODEC + +/*! + Returns a status code indicating the first error that was met by + QSettings, or QSettings::NoError if no error occurred. + + Be aware that QSettings delays performing some operations. For this + reason, you might want to call sync() to ensure that the data stored + in QSettings is written to disk before calling status(). + + \sa sync() +*/ +QSettings::Status QSettings::status() const +{ + Q_D(const QSettings); + return d->status; +} + +/*! + Appends \a prefix to the current group. + + The current group is automatically prepended to all keys + specified to QSettings. In addition, query functions such as + childGroups(), childKeys(), and allKeys() are based on the group. + By default, no group is set. + + Groups are useful to avoid typing in the same setting paths over + and over. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 13 + + This will set the value of three settings: + + \list + \o \c mainwindow/size + \o \c mainwindow/fullScreen + \o \c outputpanel/visible + \endlist + + Call endGroup() to reset the current group to what it was before + the corresponding beginGroup() call. Groups can be nested. + + \sa endGroup(), group() +*/ +void QSettings::beginGroup(const QString &prefix) +{ + Q_D(QSettings); + d->beginGroupOrArray(QSettingsGroup(d->normalizedKey(prefix))); +} + +/*! + Resets the group to what it was before the corresponding + beginGroup() call. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 14 + + \sa beginGroup(), group() +*/ +void QSettings::endGroup() +{ + Q_D(QSettings); + if (d->groupStack.isEmpty()) { + qWarning("QSettings::endGroup: No matching beginGroup()"); + return; + } + + QSettingsGroup group = d->groupStack.pop(); + int len = group.toString().size(); + if (len > 0) + d->groupPrefix.truncate(d->groupPrefix.size() - (len + 1)); + + if (group.isArray()) + qWarning("QSettings::endGroup: Expected endArray() instead"); +} + +/*! + Returns the current group. + + \sa beginGroup(), endGroup() +*/ +QString QSettings::group() const +{ + Q_D(const QSettings); + return d->groupPrefix.left(d->groupPrefix.size() - 1); +} + +/*! + Adds \a prefix to the current group and starts reading from an + array. Returns the size of the array. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 15 + + Use beginWriteArray() to write the array in the first place. + + \sa beginWriteArray(), endArray(), setArrayIndex() +*/ +int QSettings::beginReadArray(const QString &prefix) +{ + Q_D(QSettings); + d->beginGroupOrArray(QSettingsGroup(d->normalizedKey(prefix), false)); + return value(QLatin1String("size")).toInt(); +} + +/*! + Adds \a prefix to the current group and starts writing an array + of size \a size. If \a size is -1 (the default), it is automatically + determined based on the indexes of the entries written. + + If you have many occurrences of a certain set of keys, you can + use arrays to make your life easier. For example, let's suppose + that you want to save a variable-length list of user names and + passwords. You could then write: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 16 + + The generated keys will have the form + + \list + \o \c logins/size + \o \c logins/1/userName + \o \c logins/1/password + \o \c logins/2/userName + \o \c logins/2/password + \o \c logins/3/userName + \o \c logins/3/password + \o ... + \endlist + + To read back an array, use beginReadArray(). + + \sa beginReadArray(), endArray(), setArrayIndex() +*/ +void QSettings::beginWriteArray(const QString &prefix, int size) +{ + Q_D(QSettings); + d->beginGroupOrArray(QSettingsGroup(d->normalizedKey(prefix), size < 0)); + + if (size < 0) + remove(QLatin1String("size")); + else + setValue(QLatin1String("size"), size); +} + +/*! + Closes the array that was started using beginReadArray() or + beginWriteArray(). + + \sa beginReadArray(), beginWriteArray() +*/ +void QSettings::endArray() +{ + Q_D(QSettings); + if (d->groupStack.isEmpty()) { + qWarning("QSettings::endArray: No matching beginArray()"); + return; + } + + QSettingsGroup group = d->groupStack.top(); + int len = group.toString().size(); + d->groupStack.pop(); + if (len > 0) + d->groupPrefix.truncate(d->groupPrefix.size() - (len + 1)); + + if (group.arraySizeGuess() != -1) + setValue(group.name() + QLatin1String("/size"), group.arraySizeGuess()); + + if (!group.isArray()) + qWarning("QSettings::endArray: Expected endGroup() instead"); +} + +/*! + Sets the current array index to \a i. Calls to functions such as + setValue(), value(), remove(), and contains() will operate on the + array entry at that index. + + You must call beginReadArray() or beginWriteArray() before you + can call this function. +*/ +void QSettings::setArrayIndex(int i) +{ + Q_D(QSettings); + if (d->groupStack.isEmpty() || !d->groupStack.top().isArray()) { + qWarning("QSettings::setArrayIndex: Missing beginArray()"); + return; + } + + QSettingsGroup &top = d->groupStack.top(); + int len = top.toString().size(); + top.setArrayIndex(qMax(i, 0)); + d->groupPrefix.replace(d->groupPrefix.size() - len - 1, len, top.toString()); +} + +/*! + Returns a list of all keys, including subkeys, that can be read + using the QSettings object. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 17 + + If a group is set using beginGroup(), only the keys in the group + are returned, without the group prefix: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 18 + + \sa childGroups(), childKeys() +*/ +QStringList QSettings::allKeys() const +{ + Q_D(const QSettings); + return d->children(d->groupPrefix, QSettingsPrivate::AllKeys); +} + +/*! + Returns a list of all top-level keys that can be read using the + QSettings object. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 19 + + If a group is set using beginGroup(), the top-level keys in that + group are returned, without the group prefix: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 20 + + You can navigate through the entire setting hierarchy using + childKeys() and childGroups() recursively. + + \sa childGroups(), allKeys() +*/ +QStringList QSettings::childKeys() const +{ + Q_D(const QSettings); + return d->children(d->groupPrefix, QSettingsPrivate::ChildKeys); +} + +/*! + Returns a list of all key top-level groups that contain keys that + can be read using the QSettings object. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 21 + + If a group is set using beginGroup(), the first-level keys in + that group are returned, without the group prefix. + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 22 + + You can navigate through the entire setting hierarchy using + childKeys() and childGroups() recursively. + + \sa childKeys(), allKeys() +*/ +QStringList QSettings::childGroups() const +{ + Q_D(const QSettings); + return d->children(d->groupPrefix, QSettingsPrivate::ChildGroups); +} + +/*! + Returns true if settings can be written using this QSettings + object; returns false otherwise. + + One reason why isWritable() might return false is if + QSettings operates on a read-only file. + + \warning This function is not perfectly reliable, because the + file permissions can change at any time. + + \sa fileName(), status(), sync() +*/ +bool QSettings::isWritable() const +{ + Q_D(const QSettings); + return d->isWritable(); +} + +/*! + + Sets the value of setting \a key to \a value. If the \a key already + exists, the previous value is overwritten. + + Note that the Windows registry and INI files use case-insensitive + keys, whereas the Carbon Preferences API on Mac OS X uses + case-sensitive keys. To avoid portability problems, see the + \l{Section and Key Syntax} rules. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 23 + + \sa value(), remove(), contains() +*/ +void QSettings::setValue(const QString &key, const QVariant &value) +{ + Q_D(QSettings); + QString k = d->actualKey(key); + d->set(k, value); + d->requestUpdate(); +} + +/*! + Removes the setting \a key and any sub-settings of \a key. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 24 + + Be aware that if one of the fallback locations contains a setting + with the same key, that setting will be visible after calling + remove(). + + If \a key is an empty string, all keys in the current group() are + removed. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 25 + + Note that the Windows registry and INI files use case-insensitive + keys, whereas the Carbon Preferences API on Mac OS X uses + case-sensitive keys. To avoid portability problems, see the + \l{Section and Key Syntax} rules. + + \sa setValue(), value(), contains() +*/ +void QSettings::remove(const QString &key) +{ + Q_D(QSettings); + /* + We cannot use actualKey(), because remove() supports empty + keys. The code is also tricky because of slash handling. + */ + QString theKey = d->normalizedKey(key); + if (theKey.isEmpty()) + theKey = group(); + else + theKey.prepend(d->groupPrefix); + + if (theKey.isEmpty()) { + d->clear(); + } else { + d->remove(theKey); + } + d->requestUpdate(); +} + +/*! + Returns true if there exists a setting called \a key; returns + false otherwise. + + If a group is set using beginGroup(), \a key is taken to be + relative to that group. + + Note that the Windows registry and INI files use case-insensitive + keys, whereas the Carbon Preferences API on Mac OS X uses + case-sensitive keys. To avoid portability problems, see the + \l{Section and Key Syntax} rules. + + \sa value(), setValue() +*/ +bool QSettings::contains(const QString &key) const +{ + Q_D(const QSettings); + QString k = d->actualKey(key); + return d->get(k, 0); +} + +/*! + Sets whether fallbacks are enabled to \a b. + + By default, fallbacks are enabled. + + \sa fallbacksEnabled() +*/ +void QSettings::setFallbacksEnabled(bool b) +{ + Q_D(QSettings); + d->fallbacks = !!b; +} + +/*! + Returns true if fallbacks are enabled; returns false otherwise. + + By default, fallbacks are enabled. + + \sa setFallbacksEnabled() +*/ +bool QSettings::fallbacksEnabled() const +{ + Q_D(const QSettings); + return d->fallbacks; +} + +#ifndef QT_NO_QOBJECT +/*! + \reimp +*/ +bool QSettings::event(QEvent *event) +{ + Q_D(QSettings); + if (event->type() == QEvent::UpdateRequest) { + d->update(); + return true; + } + return QObject::event(event); +} +#endif + +/*! + Returns the value for setting \a key. If the setting doesn't + exist, returns \a defaultValue. + + If no default value is specified, a default QVariant is + returned. + + Note that the Windows registry and INI files use case-insensitive + keys, whereas the Carbon Preferences API on Mac OS X uses + case-sensitive keys. To avoid portability problems, see the + \l{Section and Key Syntax} rules. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 26 + + \sa setValue(), contains(), remove() +*/ +QVariant QSettings::value(const QString &key, const QVariant &defaultValue) const +{ + Q_D(const QSettings); + QVariant result = defaultValue; + QString k = d->actualKey(key); + d->get(k, &result); + return result; +} + +/*! + \since 4.4 + + Sets the default file format to the given \a format, which is used + for storing settings for the QSettings(QObject *) constructor. + + If no default format is set, QSettings::NativeFormat is used. See + the documentation for the QSettings constructor you are using to + see if that constructor will ignore this function. + + \sa format() +*/ +void QSettings::setDefaultFormat(Format format) +{ + globalDefaultFormat = format; +} + +/*! + \since 4.4 + + Returns default file format used for storing settings for the QSettings(QObject *) constructor. + If no default format is set, QSettings::NativeFormat is used. + + \sa format() +*/ +QSettings::Format QSettings::defaultFormat() +{ + return globalDefaultFormat; +} + +/*! + \obsolete + + Use setPath() instead. + + \oldcode + setSystemIniPath(path); + \newcode + setPath(QSettings::NativeFormat, QSettings::SystemScope, path); + setPath(QSettings::IniFormat, QSettings::SystemScope, path); + \endcode +*/ +void QSettings::setSystemIniPath(const QString &dir) +{ + setPath(IniFormat, SystemScope, dir); +#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) + setPath(NativeFormat, SystemScope, dir); +#endif +} + +/*! + \obsolete + + Use setPath() instead. +*/ + +void QSettings::setUserIniPath(const QString &dir) +{ + setPath(IniFormat, UserScope, dir); +#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) + setPath(NativeFormat, UserScope, dir); +#endif +} + +/*! + \since 4.1 + + Sets the path used for storing settings for the given \a format + and \a scope, to \a path. The \a format can be a custom format. + + The table below summarizes the default values: + + \table + \header \o Platform \o Format \o Scope \o Path + \row \o{1,2} Windows \o{1,2} IniFormat \o UserScope \o \c %APPDATA% + \row \o SystemScope \o \c %COMMON_APPDATA% + \row \o{1,2} Unix \o{1,2} NativeFormat, IniFormat \o UserScope \o \c $HOME/.config + \row \o SystemScope \o \c /etc/xdg + \row \o{1,2} Qt for Embedded Linux \o{1,2} NativeFormat, IniFormat \o UserScope \o \c $HOME/Settings + \row \o SystemScope \o \c /etc/xdg + \row \o{1,2} Mac OS X \o{1,2} IniFormat \o UserScope \o \c $HOME/.config + \row \o SystemScope \o \c /etc/xdg + \endtable + + The default UserScope paths on Unix and Mac OS X (\c + $HOME/.config or $HOME/Settings) can be overridden by the user by setting the + \c XDG_CONFIG_HOME environment variable. The default SystemScope + paths on Unix and Mac OS X (\c /etc/xdg) can be overridden when + building the Qt library using the \c configure script's \c + --sysconfdir flag (see QLibraryInfo for details). + + Setting the NativeFormat paths on Windows and Mac OS X has no + effect. + + \warning This function doesn't affect existing QSettings objects. + + \sa registerFormat() +*/ +void QSettings::setPath(Format format, Scope scope, const QString &path) +{ + QMutexLocker locker(globalMutex()); + PathHash *pathHash = pathHashFunc(); + if (pathHash->isEmpty()) + initDefaultPaths(&locker); + pathHash->insert(pathHashKey(format, scope), path + QDir::separator()); +} + +/*! + \typedef QSettings::SettingsMap + + Typedef for QMap<QString, QVariant>. + + \sa registerFormat() +*/ + +/*! + \typedef QSettings::ReadFunc + + Typedef for a pointer to a function with the following signature: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 27 + + \c ReadFunc is used in \c registerFormat() as a pointer to a function + that reads a set of key/value pairs. \c ReadFunc should read all the + options in one pass, and return all the settings in the \c SettingsMap + container, which is initially empty. + + \sa WriteFunc, registerFormat() +*/ + +/*! + \typedef QSettings::WriteFunc + + Typedef for a pointer to a function with the following signature: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 28 + + \c WriteFunc is used in \c registerFormat() as a pointer to a function + that writes a set of key/value pairs. \c WriteFunc is only called once, + so you need to output the settings in one go. + + \sa ReadFunc, registerFormat() +*/ + +/*! + \since 4.1 + \threadsafe + + Registers a custom storage format. On success, returns a special + Format value that can then be passed to the QSettings constructor. + On failure, returns InvalidFormat. + + The \a extension is the file + extension associated to the format (without the '.'). + + The \a readFunc and \a writeFunc parameters are pointers to + functions that read and write a set of key/value pairs. The + QIODevice parameter to the read and write functions is always + opened in binary mode (i.e., without the QIODevice::Text flag). + + The \a caseSensitivity parameter specifies whether keys are case + sensitive or not. This makes a difference when looking up values + using QSettings. The default is case sensitive. + + By default, if you use one of the constructors that work in terms + of an organization name and an application name, the file system + locations used are the same as for IniFormat. Use setPath() to + specify other locations. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qsettings.cpp 29 + + \sa setPath() +*/ +QSettings::Format QSettings::registerFormat(const QString &extension, ReadFunc readFunc, + WriteFunc writeFunc, + Qt::CaseSensitivity caseSensitivity) +{ +#ifdef QT_QSETTINGS_ALWAYS_CASE_SENSITIVE_AND_FORGET_ORIGINAL_KEY_ORDER + Q_ASSERT(caseSensitivity == Qt::CaseSensitive); +#endif + + QMutexLocker locker(globalMutex()); + CustomFormatVector *customFormatVector = customFormatVectorFunc(); + int index = customFormatVector->size(); + if (index == 16) // the QSettings::Format enum has room for 16 custom formats + return QSettings::InvalidFormat; + + QConfFileCustomFormat info; + info.extension = QLatin1Char('.'); + info.extension += extension; + info.readFunc = readFunc; + info.writeFunc = writeFunc; + info.caseSensitivity = caseSensitivity; + customFormatVector->append(info); + + return QSettings::Format((int)QSettings::CustomFormat1 + index); +} + +#ifdef QT3_SUPPORT +void QSettings::setPath_helper(Scope scope, const QString &organization, const QString &application) +{ + Q_D(QSettings); + if (d->pendingChanges) + d->flush(); + QSettingsPrivate *oldPriv = d; + QSettingsPrivate *newPriv = QSettingsPrivate::create(oldPriv->format, scope, organization, application); + static_cast<QObjectPrivate &>(*newPriv) = static_cast<QObjectPrivate &>(*oldPriv); // copy the QObject stuff over (hack) + d_ptr.reset(newPriv); +} + +/*! \fn bool QSettings::writeEntry(const QString &key, bool value) + + Sets the value of setting \a key to \a value. + + Use setValue() instead. +*/ + +/*! \fn bool QSettings::writeEntry(const QString &key, double value) + + \overload +*/ + +/*! \fn bool QSettings::writeEntry(const QString &key, int value) + + \overload +*/ + +/*! \fn bool QSettings::writeEntry(const QString &key, const char *value) + + \overload +*/ + +/*! \fn bool QSettings::writeEntry(const QString &key, const QString &value) + + \overload +*/ + +/*! \fn bool QSettings::writeEntry(const QString &key, const QStringList &value) + + \overload +*/ + +/*! \fn bool QSettings::writeEntry(const QString &key, const QStringList &value, QChar separator) + + \overload + + Use setValue(\a key, \a value) instead. You don't need \a separator. +*/ + +/*! \fn QStringList QSettings::readListEntry(const QString &key, bool *ok = 0) + + Returns the value of setting \a key converted to a QStringList. + + If \a ok is not 0, *\a{ok} is set to true if the key exists, + otherwise *\a{ok} is set to false. + + Use value() instead. + + \oldcode + bool ok; + QStringList list = settings.readListEntry("recentFiles", &ok); + \newcode + bool ok = settings.contains("recentFiles"); + QStringList list = settings.value("recentFiles").toStringList(); + \endcode +*/ + +/*! \fn QStringList QSettings::readListEntry(const QString &key, QChar separator, bool *ok) + + Returns the value of setting \a key converted to a QStringList. + \a separator is ignored. + + If \a ok is not 0, *\a{ok} is set to true if the key exists, + otherwise *\a{ok} is set to false. + + Use value() instead. + + \oldcode + bool ok; + QStringList list = settings.readListEntry("recentFiles", ":", &ok); + \newcode + bool ok = settings.contains("recentFiles"); + QStringList list = settings.value("recentFiles").toStringList(); + \endcode +*/ + +/*! \fn QString QSettings::readEntry(const QString &key, const QString &defaultValue, bool *ok) + + Returns the value for setting \a key converted to a QString. If + the setting doesn't exist, returns \a defaultValue. + + If \a ok is not 0, *\a{ok} is set to true if the key exists, + otherwise *\a{ok} is set to false. + + Use value() instead. + + \oldcode + bool ok; + QString str = settings.readEntry("userName", "administrator", &ok); + \newcode + bool ok = settings.contains("userName"); + QString str = settings.value("userName", "administrator").toString(); + \endcode +*/ + +/*! \fn int QSettings::readNumEntry(const QString &key, int defaultValue, bool *ok) + + Returns the value for setting \a key converted to an \c int. If + the setting doesn't exist, returns \a defaultValue. + + If \a ok is not 0, *\a{ok} is set to true if the key exists, + otherwise *\a{ok} is set to false. + + Use value() instead. + + \oldcode + bool ok; + int max = settings.readNumEntry("maxConnections", 30, &ok); + \newcode + bool ok = settings.contains("maxConnections"); + int max = settings.value("maxConnections", 30).toInt(); + \endcode +*/ + +/*! \fn double QSettings::readDoubleEntry(const QString &key, double defaultValue, bool *ok) + + Returns the value for setting \a key converted to a \c double. If + the setting doesn't exist, returns \a defaultValue. + + If \a ok is not 0, *\a{ok} is set to true if the key exists, + otherwise *\a{ok} is set to false. + + Use value() instead. + + \oldcode + bool ok; + double pi = settings.readDoubleEntry("pi", 3.141592, &ok); + \newcode + bool ok = settings.contains("pi"); + double pi = settings.value("pi", 3.141592).toDouble(); + \endcode +*/ + +/*! \fn bool QSettings::readBoolEntry(const QString &key, bool defaultValue, bool *ok) + + Returns the value for setting \a key converted to a \c bool. If + the setting doesn't exist, returns \a defaultValue. + + If \a ok is not 0, *\a{ok} is set to true if the key exists, + otherwise *\a{ok} is set to false. + + Use value() instead. + + \oldcode + bool ok; + bool grid = settings.readBoolEntry("showGrid", true, &ok); + \newcode + bool ok = settings.contains("showGrid"); + bool grid = settings.value("showGrid", true).toBool(); + \endcode +*/ + +/*! \fn bool QSettings::removeEntry(const QString &key) + + Use remove() instead. +*/ + +/*! \enum QSettings::System + \compat + + \value Unix Unix systems (X11 and Embedded Linux) + \value Windows Microsoft Windows systems + \value Mac Mac OS X systems + + \sa insertSearchPath(), removeSearchPath() +*/ + +/*! \fn void QSettings::insertSearchPath(System system, const QString &path) + + This function is implemented as a no-op. It is provided for + source compatibility with Qt 3. The new QSettings class has no + concept of "search path". +*/ + +/*! \fn void QSettings::removeSearchPath(System system, const QString &path) + + This function is implemented as a no-op. It is provided for + source compatibility with Qt 3. The new QSettings class has no + concept of "search path". +*/ + +/*! \fn void QSettings::setPath(const QString &organization, const QString &application, \ + Scope scope) + + Specifies the \a organization, \a application, and \a scope to + use by the QSettings object. + + Use the appropriate constructor instead, with QSettings::UserScope + instead of QSettings::User and QSettings::SystemScope instead of + QSettings::Global. + + \oldcode + QSettings settings; + settings.setPath("twikimaster.com", "Kanooth", QSettings::Global); + \newcode + QSettings settings(QSettings::SystemScope, "twikimaster.com", "Kanooth"); + \endcode +*/ + +/*! \fn void QSettings::resetGroup() + + Sets the current group to be the empty string. + + Use endGroup() instead (possibly multiple times). + + \oldcode + QSettings settings; + settings.beginGroup("mainWindow"); + settings.beginGroup("leftPanel"); + ... + settings.resetGroup(); + \newcode + QSettings settings; + settings.beginGroup("mainWindow"); + settings.beginGroup("leftPanel"); + ... + settings.endGroup(); + settings.endGroup(); + \endcode +*/ + +/*! \fn QStringList QSettings::entryList(const QString &key) const + + Returns a list of all sub-keys of \a key. + + Use childKeys() instead. + + \oldcode + QSettings settings; + QStringList keys = settings.entryList("cities"); + ... + \newcode + QSettings settings; + settings.beginGroup("cities"); + QStringList keys = settings.childKeys(); + ... + settings.endGroup(); + \endcode +*/ + +/*! \fn QStringList QSettings::subkeyList(const QString &key) const + + Returns a list of all sub-keys of \a key. + + Use childGroups() instead. + + \oldcode + QSettings settings; + QStringList groups = settings.entryList("cities"); + ... + \newcode + QSettings settings; + settings.beginGroup("cities"); + QStringList groups = settings.childKeys(); + ... + settings.endGroup(); + \endcode +*/ +#endif + +QT_END_NAMESPACE + +#endif // QT_NO_SETTINGS diff --git a/src/corelib/io/qsettings.h b/src/corelib/io/qsettings.h new file mode 100644 index 0000000000..b62446ee3b --- /dev/null +++ b/src/corelib/io/qsettings.h @@ -0,0 +1,313 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSETTINGS_H +#define QSETTINGS_H + +#include <QtCore/qobject.h> +#include <QtCore/qvariant.h> +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE +QT_MODULE(Core) +QT_END_NAMESPACE + +#ifndef QT_NO_SETTINGS + +#ifdef QT3_SUPPORT +#include <QtCore/qstringlist.h> +#endif + +#include <ctype.h> + +QT_BEGIN_NAMESPACE + +#ifdef Status // we seem to pick up a macro Status --> int somewhere +#undef Status +#endif + +class QIODevice; +class QSettingsPrivate; + +#ifndef QT_NO_QOBJECT +class Q_CORE_EXPORT QSettings : public QObject +#else +class Q_CORE_EXPORT QSettings +#endif +{ +#ifndef QT_NO_QOBJECT + Q_OBJECT +#else + QScopedPointer<QSettingsPrivate> d_ptr; +#endif + Q_DECLARE_PRIVATE(QSettings) + +public: + enum Status { + NoError = 0, + AccessError, + FormatError + }; + + enum Format { + NativeFormat, + IniFormat, + + InvalidFormat = 16, + CustomFormat1, + CustomFormat2, + CustomFormat3, + CustomFormat4, + CustomFormat5, + CustomFormat6, + CustomFormat7, + CustomFormat8, + CustomFormat9, + CustomFormat10, + CustomFormat11, + CustomFormat12, + CustomFormat13, + CustomFormat14, + CustomFormat15, + CustomFormat16 + }; + + enum Scope { + UserScope, + SystemScope +#ifdef QT3_SUPPORT + , + User = UserScope, + Global = SystemScope +#endif + }; + +#ifndef QT_NO_QOBJECT + explicit QSettings(const QString &organization, + const QString &application = QString(), QObject *parent = 0); + QSettings(Scope scope, const QString &organization, + const QString &application = QString(), QObject *parent = 0); + QSettings(Format format, Scope scope, const QString &organization, + const QString &application = QString(), QObject *parent = 0); + QSettings(const QString &fileName, Format format, QObject *parent = 0); + explicit QSettings(QObject *parent = 0); +#else + explicit QSettings(const QString &organization, + const QString &application = QString()); + QSettings(Scope scope, const QString &organization, + const QString &application = QString()); + QSettings(Format format, Scope scope, const QString &organization, + const QString &application = QString()); + QSettings(const QString &fileName, Format format); +#endif + ~QSettings(); + + void clear(); + void sync(); + Status status() const; + + void beginGroup(const QString &prefix); + void endGroup(); + QString group() const; + + int beginReadArray(const QString &prefix); + void beginWriteArray(const QString &prefix, int size = -1); + void endArray(); + void setArrayIndex(int i); + + QStringList allKeys() const; + QStringList childKeys() const; + QStringList childGroups() const; + bool isWritable() const; + + void setValue(const QString &key, const QVariant &value); + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + + void remove(const QString &key); + bool contains(const QString &key) const; + + void setFallbacksEnabled(bool b); + bool fallbacksEnabled() const; + + QString fileName() const; + Format format() const; + Scope scope() const; + QString organizationName() const; + QString applicationName() const; + +#ifndef QT_NO_TEXTCODEC + void setIniCodec(QTextCodec *codec); + void setIniCodec(const char *codecName); + QTextCodec *iniCodec() const; +#endif + + static void setDefaultFormat(Format format); + static Format defaultFormat(); + static void setSystemIniPath(const QString &dir); // ### remove in 5.0 (use setPath() instead) + static void setUserIniPath(const QString &dir); // ### remove in 5.0 (use setPath() instead) + static void setPath(Format format, Scope scope, const QString &path); + + typedef QMap<QString, QVariant> SettingsMap; + typedef bool (*ReadFunc)(QIODevice &device, SettingsMap &map); + typedef bool (*WriteFunc)(QIODevice &device, const SettingsMap &map); + + static Format registerFormat(const QString &extension, ReadFunc readFunc, WriteFunc writeFunc, + Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive); + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT bool writeEntry(const QString &key, bool value) + { setValue(key, value); return isWritable(); } + inline QT3_SUPPORT bool writeEntry(const QString &key, double value) + { setValue(key, value); return isWritable(); } + inline QT3_SUPPORT bool writeEntry(const QString &key, int value) + { setValue(key, value); return isWritable(); } + inline QT3_SUPPORT bool writeEntry(const QString &key, const char *value) + { setValue(key, QString::fromAscii(value)); return isWritable(); } + inline QT3_SUPPORT bool writeEntry(const QString &key, const QString &value) + { setValue(key, value); return isWritable(); } + inline QT3_SUPPORT bool writeEntry(const QString &key, const QStringList &value) + { setValue(key, value); return isWritable(); } + inline QT3_SUPPORT bool writeEntry(const QString &key, const QStringList &value, QChar separator) + { setValue(key, value.join(QString(separator))); return isWritable(); } + inline QT3_SUPPORT QStringList readListEntry(const QString &key, bool *ok = 0) + { + if (ok) + *ok = contains(key); + return value(key).toStringList(); + } + inline QT3_SUPPORT QStringList readListEntry(const QString &key, QChar separator, bool *ok = 0) + { + if (ok) + *ok = contains(key); + QString str = value(key).toString(); + if (str.isEmpty()) + return QStringList(); + return str.split(separator); + } + inline QT3_SUPPORT QString readEntry(const QString &key, const QString &defaultValue = QString(), + bool *ok = 0) + { + if (ok) + *ok = contains(key); + return value(key, defaultValue).toString(); + } + inline QT3_SUPPORT int readNumEntry(const QString &key, int defaultValue = 0, bool *ok = 0) + { + if (ok) + *ok = contains(key); + return value(key, defaultValue).toInt(); + } + inline QT3_SUPPORT double readDoubleEntry(const QString &key, double defaultValue = 0, + bool *ok = 0) + { + if (ok) + *ok = contains(key); + return value(key, defaultValue).toDouble(); + } + inline QT3_SUPPORT bool readBoolEntry(const QString &key, bool defaultValue = false, + bool *ok = 0) + { + if (ok) + *ok = contains(key); + return value(key, defaultValue).toBool(); + } + inline QT3_SUPPORT bool removeEntry(const QString &key) + { remove(key); return true; } + + enum System { Unix, Windows, Mac }; + inline QT3_SUPPORT void insertSearchPath(System, const QString &) {} + inline QT3_SUPPORT void removeSearchPath(System, const QString &) {} + + inline QT3_SUPPORT void setPath(const QString &organization, const QString &application, + Scope scope = Global) + { + setPath_helper(scope == Global ? QSettings::SystemScope : QSettings::UserScope, + organization, application); + } + inline QT3_SUPPORT void resetGroup() + { + while (!group().isEmpty()) + endGroup(); + } + inline QT3_SUPPORT QStringList entryList(const QString &key) const + { + QSettings *that = const_cast<QSettings *>(this); + QStringList result; + + that->beginGroup(key); + result = that->childKeys(); + that->endGroup(); + return result; + } + inline QT3_SUPPORT QStringList subkeyList(const QString &key) const + { + QSettings *that = const_cast<QSettings *>(this); + QStringList result; + + that->beginGroup(key); + result = that->childGroups(); + that->endGroup(); + return result; + } +#endif + +protected: +#ifndef QT_NO_QOBJECT + bool event(QEvent *event); +#endif + +private: +#ifdef QT3_SUPPORT + void setPath_helper(Scope scope, const QString &organization, const QString &application); +#endif + + Q_DISABLE_COPY(QSettings) +}; + +QT_END_NAMESPACE + +#endif // QT_NO_SETTINGS + +QT_END_HEADER + +#endif // QSETTINGS_H diff --git a/src/corelib/io/qsettings_mac.cpp b/src/corelib/io/qsettings_mac.cpp new file mode 100644 index 0000000000..6bc41a6afb --- /dev/null +++ b/src/corelib/io/qsettings_mac.cpp @@ -0,0 +1,654 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsettings.h" +#include "qsettings_p.h" +#include "qdatetime.h" +#include "qdir.h" +#include "qvarlengtharray.h" +#include "private/qcore_mac_p.h" + +QT_BEGIN_NAMESPACE + +static const CFStringRef hostNames[2] = { kCFPreferencesCurrentHost, kCFPreferencesAnyHost }; +static const int numHostNames = 2; + +/* + On the Mac, it is more natural to use '.' as the key separator + than '/'. Therefore, it makes sense to replace '/' with '.' in + keys. Then we replace '.' with middle dots (which we can't show + here) and middle dots with '/'. A key like "4.0/BrowserCommand" + becomes "4<middot>0.BrowserCommand". +*/ + +enum RotateShift { Macify = 1, Qtify = 2 }; + +static QString rotateSlashesDotsAndMiddots(const QString &key, int shift) +{ + static const int NumKnights = 3; + static const char knightsOfTheRoundTable[NumKnights] = { '/', '.', '\xb7' }; + QString result = key; + + for (int i = 0; i < result.size(); ++i) { + for (int j = 0; j < NumKnights; ++j) { + if (result.at(i) == QLatin1Char(knightsOfTheRoundTable[j])) { + result[i] = QLatin1Char(knightsOfTheRoundTable[(j + shift) % NumKnights]).unicode(); + break; + } + } + } + return result; +} + +static QCFType<CFStringRef> macKey(const QString &key) +{ + return QCFString::toCFStringRef(rotateSlashesDotsAndMiddots(key, Macify)); +} + +static QString qtKey(CFStringRef cfkey) +{ + return rotateSlashesDotsAndMiddots(QCFString::toQString(cfkey), Qtify); +} + +static QCFType<CFPropertyListRef> macValue(const QVariant &value); + +static CFArrayRef macList(const QList<QVariant> &list) +{ + int n = list.size(); + QVarLengthArray<QCFType<CFPropertyListRef> > cfvalues(n); + for (int i = 0; i < n; ++i) + cfvalues[i] = macValue(list.at(i)); + return CFArrayCreate(kCFAllocatorDefault, reinterpret_cast<const void **>(cfvalues.data()), + CFIndex(n), &kCFTypeArrayCallBacks); +} + +static QCFType<CFPropertyListRef> macValue(const QVariant &value) +{ + CFPropertyListRef result = 0; + + switch (value.type()) { + case QVariant::ByteArray: + { + QByteArray ba = value.toByteArray(); + result = CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(ba.data()), + CFIndex(ba.size())); + } + break; + // should be same as below (look for LIST) + case QVariant::List: + case QVariant::StringList: + case QVariant::Polygon: + result = macList(value.toList()); + break; + case QVariant::Map: + { + /* + QMap<QString, QVariant> is potentially a multimap, + whereas CFDictionary is a single-valued map. To allow + for multiple values with the same key, we store + multiple values in a CFArray. To avoid ambiguities, + we also wrap lists in a CFArray singleton. + */ + QMap<QString, QVariant> map = value.toMap(); + QMap<QString, QVariant>::const_iterator i = map.constBegin(); + + int maxUniqueKeys = map.size(); + int numUniqueKeys = 0; + QVarLengthArray<QCFType<CFPropertyListRef> > cfkeys(maxUniqueKeys); + QVarLengthArray<QCFType<CFPropertyListRef> > cfvalues(maxUniqueKeys); + + while (i != map.constEnd()) { + const QString &key = i.key(); + QList<QVariant> values; + + do { + values << i.value(); + ++i; + } while (i != map.constEnd() && i.key() == key); + + bool singleton = (values.count() == 1); + if (singleton) { + switch (values.first().type()) { + // should be same as above (look for LIST) + case QVariant::List: + case QVariant::StringList: + case QVariant::Polygon: + singleton = false; + default: + ; + } + } + + cfkeys[numUniqueKeys] = QCFString::toCFStringRef(key); + cfvalues[numUniqueKeys] = singleton ? macValue(values.first()) : macList(values); + ++numUniqueKeys; + } + + result = CFDictionaryCreate(kCFAllocatorDefault, + reinterpret_cast<const void **>(cfkeys.data()), + reinterpret_cast<const void **>(cfvalues.data()), + CFIndex(numUniqueKeys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + } + break; + case QVariant::DateTime: + { + /* + CFDate, unlike QDateTime, doesn't store timezone information. + */ + QDateTime dt = value.toDateTime(); + if (dt.timeSpec() == Qt::LocalTime) { + QDateTime reference; + reference.setTime_t((uint)kCFAbsoluteTimeIntervalSince1970); + result = CFDateCreate(kCFAllocatorDefault, CFAbsoluteTime(reference.secsTo(dt))); + } else { + goto string_case; + } + } + break; + case QVariant::Bool: + result = value.toBool() ? kCFBooleanTrue : kCFBooleanFalse; + break; + case QVariant::Int: + case QVariant::UInt: + { + int n = value.toInt(); + result = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &n); + } + break; + case QVariant::Double: + { + double n = value.toDouble(); + result = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &n); + } + break; + case QVariant::LongLong: + case QVariant::ULongLong: + { + qint64 n = value.toLongLong(); + result = CFNumberCreate(0, kCFNumberLongLongType, &n); + } + break; + case QVariant::String: + string_case: + default: + result = QCFString::toCFStringRef(QSettingsPrivate::variantToString(value)); + } + return result; +} + +static QVariant qtValue(CFPropertyListRef cfvalue) +{ + if (!cfvalue) + return QVariant(); + + CFTypeID typeId = CFGetTypeID(cfvalue); + + /* + Sorted grossly from most to least frequent type. + */ + if (typeId == CFStringGetTypeID()) { + return QSettingsPrivate::stringToVariant(QCFString::toQString(static_cast<CFStringRef>(cfvalue))); + } else if (typeId == CFNumberGetTypeID()) { + CFNumberRef cfnumber = static_cast<CFNumberRef>(cfvalue); + if (CFNumberIsFloatType(cfnumber)) { + double d; + CFNumberGetValue(cfnumber, kCFNumberDoubleType, &d); + return d; + } else { + int i; + qint64 ll; + + if (CFNumberGetValue(cfnumber, kCFNumberIntType, &i)) + return i; + CFNumberGetValue(cfnumber, kCFNumberLongLongType, &ll); + return ll; + } + } else if (typeId == CFArrayGetTypeID()) { + CFArrayRef cfarray = static_cast<CFArrayRef>(cfvalue); + QList<QVariant> list; + CFIndex size = CFArrayGetCount(cfarray); + bool metNonString = false; + for (CFIndex i = 0; i < size; ++i) { + QVariant value = qtValue(CFArrayGetValueAtIndex(cfarray, i)); + if (value.type() != QVariant::String) + metNonString = true; + list << value; + } + if (metNonString) + return list; + else + return QVariant(list).toStringList(); + } else if (typeId == CFBooleanGetTypeID()) { + return (bool)CFBooleanGetValue(static_cast<CFBooleanRef>(cfvalue)); + } else if (typeId == CFDataGetTypeID()) { + CFDataRef cfdata = static_cast<CFDataRef>(cfvalue); + return QByteArray(reinterpret_cast<const char *>(CFDataGetBytePtr(cfdata)), + CFDataGetLength(cfdata)); + } else if (typeId == CFDictionaryGetTypeID()) { + CFDictionaryRef cfdict = static_cast<CFDictionaryRef>(cfvalue); + CFTypeID arrayTypeId = CFArrayGetTypeID(); + int size = (int)CFDictionaryGetCount(cfdict); + QVarLengthArray<CFPropertyListRef> keys(size); + QVarLengthArray<CFPropertyListRef> values(size); + CFDictionaryGetKeysAndValues(cfdict, keys.data(), values.data()); + + QMultiMap<QString, QVariant> map; + for (int i = 0; i < size; ++i) { + QString key = QCFString::toQString(static_cast<CFStringRef>(keys[i])); + + if (CFGetTypeID(values[i]) == arrayTypeId) { + CFArrayRef cfarray = static_cast<CFArrayRef>(values[i]); + CFIndex arraySize = CFArrayGetCount(cfarray); + for (CFIndex j = arraySize - 1; j >= 0; --j) + map.insert(key, qtValue(CFArrayGetValueAtIndex(cfarray, j))); + } else { + map.insert(key, qtValue(values[i])); + } + } + return map; + } else if (typeId == CFDateGetTypeID()) { + QDateTime dt; + dt.setTime_t((uint)kCFAbsoluteTimeIntervalSince1970); + return dt.addSecs((int)CFDateGetAbsoluteTime(static_cast<CFDateRef>(cfvalue))); + } + return QVariant(); +} + +static QString comify(const QString &organization) +{ + for (int i = organization.size() - 1; i >= 0; --i) { + QChar ch = organization.at(i); + if (ch == QLatin1Char('.') || ch == QChar(0x3002) || ch == QChar(0xff0e) + || ch == QChar(0xff61)) { + QString suffix = organization.mid(i + 1).toLower(); + if (suffix.size() == 2 || suffix == QLatin1String("com") + || suffix == QLatin1String("org") || suffix == QLatin1String("net") + || suffix == QLatin1String("edu") || suffix == QLatin1String("gov") + || suffix == QLatin1String("mil") || suffix == QLatin1String("biz") + || suffix == QLatin1String("info") || suffix == QLatin1String("name") + || suffix == QLatin1String("pro") || suffix == QLatin1String("aero") + || suffix == QLatin1String("coop") || suffix == QLatin1String("museum")) { + QString result = organization; + result.replace(QLatin1Char('/'), QLatin1Char(' ')); + return result; + } + break; + } + int uc = ch.unicode(); + if ((uc < 'a' || uc > 'z') && (uc < 'A' || uc > 'Z')) + break; + } + + QString domain; + for (int i = 0; i < organization.size(); ++i) { + QChar ch = organization.at(i); + int uc = ch.unicode(); + if ((uc >= 'a' && uc <= 'z') || (uc >= '0' && uc <= '9')) { + domain += ch; + } else if (uc >= 'A' && uc <= 'Z') { + domain += ch.toLower(); + } else { + domain += QLatin1Char(' '); + } + } + domain = domain.simplified(); + domain.replace(QLatin1Char(' '), QLatin1Char('-')); + if (!domain.isEmpty()) + domain.append(QLatin1String(".com")); + return domain; +} + +class QMacSettingsPrivate : public QSettingsPrivate +{ +public: + QMacSettingsPrivate(QSettings::Scope scope, const QString &organization, + const QString &application); + ~QMacSettingsPrivate(); + + void remove(const QString &key); + void set(const QString &key, const QVariant &value); + bool get(const QString &key, QVariant *value) const; + QStringList children(const QString &prefix, ChildSpec spec) const; + void clear(); + void sync(); + void flush(); + bool isWritable() const; + QString fileName() const; + +private: + struct SearchDomain + { + CFStringRef userName; + CFStringRef applicationOrSuiteId; + }; + + QCFString applicationId; + QCFString suiteId; + QCFString hostName; + SearchDomain domains[6]; + int numDomains; +}; + +QMacSettingsPrivate::QMacSettingsPrivate(QSettings::Scope scope, const QString &organization, + const QString &application) + : QSettingsPrivate(QSettings::NativeFormat, scope, organization, application) +{ + QString javaPackageName; + int curPos = 0; + int nextDot; + + QString domainName = comify(organization); + if (domainName.isEmpty()) { + setStatus(QSettings::AccessError); + domainName = QLatin1String("unknown-organization.trolltech.com"); + } + + while ((nextDot = domainName.indexOf(QLatin1Char('.'), curPos)) != -1) { + javaPackageName.prepend(domainName.mid(curPos, nextDot - curPos)); + javaPackageName.prepend(QLatin1Char('.')); + curPos = nextDot + 1; + } + javaPackageName.prepend(domainName.mid(curPos)); + javaPackageName = javaPackageName.toLower(); + if (curPos == 0) + javaPackageName.prepend(QLatin1String("com.")); + suiteId = javaPackageName; + + if (scope == QSettings::SystemScope) + spec |= F_System; + + if (application.isEmpty()) { + spec |= F_Organization; + } else { + javaPackageName += QLatin1Char('.'); + javaPackageName += application; + applicationId = javaPackageName; + } + + numDomains = 0; + for (int i = (spec & F_System) ? 1 : 0; i < 2; ++i) { + for (int j = (spec & F_Organization) ? 1 : 0; j < 3; ++j) { + SearchDomain &domain = domains[numDomains++]; + domain.userName = (i == 0) ? kCFPreferencesCurrentUser : kCFPreferencesAnyUser; + if (j == 0) + domain.applicationOrSuiteId = applicationId; + else if (j == 1) + domain.applicationOrSuiteId = suiteId; + else + domain.applicationOrSuiteId = kCFPreferencesAnyApplication; + } + } + + hostName = (scope == QSettings::SystemScope) ? kCFPreferencesCurrentHost : kCFPreferencesAnyHost; + sync(); +} + +QMacSettingsPrivate::~QMacSettingsPrivate() +{ +} + +void QMacSettingsPrivate::remove(const QString &key) +{ + QStringList keys = children(key + QLatin1Char('/'), AllKeys); + + // If i == -1, then delete "key" itself. + for (int i = -1; i < keys.size(); ++i) { + QString subKey = key; + if (i >= 0) { + subKey += QLatin1Char('/'); + subKey += keys.at(i); + } + CFPreferencesSetValue(macKey(subKey), 0, domains[0].applicationOrSuiteId, + domains[0].userName, hostName); + } +} + +void QMacSettingsPrivate::set(const QString &key, const QVariant &value) +{ + CFPreferencesSetValue(macKey(key), macValue(value), domains[0].applicationOrSuiteId, + domains[0].userName, hostName); +} + +bool QMacSettingsPrivate::get(const QString &key, QVariant *value) const +{ + QCFString k = macKey(key); + for (int i = 0; i < numDomains; ++i) { + for (int j = 0; j < numHostNames; ++j) { + QCFType<CFPropertyListRef> ret = + CFPreferencesCopyValue(k, domains[i].applicationOrSuiteId, domains[i].userName, + hostNames[j]); + if (ret) { + if (value) + *value = qtValue(ret); + return true; + } + } + + if (!fallbacks) + break; + } + return false; +} + +QStringList QMacSettingsPrivate::children(const QString &prefix, ChildSpec spec) const +{ + QMap<QString, QString> result; + int startPos = prefix.size(); + + for (int i = 0; i < numDomains; ++i) { + for (int j = 0; j < numHostNames; ++j) { + QCFType<CFArrayRef> cfarray = CFPreferencesCopyKeyList(domains[i].applicationOrSuiteId, + domains[i].userName, + hostNames[j]); + if (cfarray) { + CFIndex size = CFArrayGetCount(cfarray); + for (CFIndex k = 0; k < size; ++k) { + QString currentKey = + qtKey(static_cast<CFStringRef>(CFArrayGetValueAtIndex(cfarray, k))); + if (currentKey.startsWith(prefix)) + processChild(currentKey.mid(startPos), spec, result); + } + } + } + + if (!fallbacks) + break; + } + return result.keys(); +} + +void QMacSettingsPrivate::clear() +{ + QCFType<CFArrayRef> cfarray = CFPreferencesCopyKeyList(domains[0].applicationOrSuiteId, + domains[0].userName, hostName); + CFPreferencesSetMultiple(0, cfarray, domains[0].applicationOrSuiteId, domains[0].userName, + hostName); +} + +void QMacSettingsPrivate::sync() +{ + for (int i = 0; i < numDomains; ++i) { + for (int j = 0; j < numHostNames; ++j) { + Boolean ok = CFPreferencesSynchronize(domains[i].applicationOrSuiteId, + domains[i].userName, hostNames[j]); + // only report failures for the primary file (the one we write to) + if (!ok && i == 0 && hostNames[j] == hostName && status == QSettings::NoError) { +#if 1 + // work around what seems to be a bug in CFPreferences: + // don't report an error if there are no preferences for the application + QCFType<CFArrayRef> appIds = CFPreferencesCopyApplicationList(domains[i].userName, + hostNames[j]); + + // iterate through all the applications and see if we're there + CFIndex size = CFArrayGetCount(appIds); + for (CFIndex k = 0; k < size; ++k) { + const void *cfvalue = CFArrayGetValueAtIndex(appIds, k); + if (CFGetTypeID(cfvalue) == CFStringGetTypeID()) { + if (CFStringCompare(static_cast<CFStringRef>(cfvalue), + domains[i].applicationOrSuiteId, + kCFCompareCaseInsensitive) == kCFCompareEqualTo) { + setStatus(QSettings::AccessError); + break; + } + } + } +#else + setStatus(QSettings::AccessError); +#endif + } + } + } +} + +void QMacSettingsPrivate::flush() +{ + sync(); +} + +bool QMacSettingsPrivate::isWritable() const +{ + QMacSettingsPrivate *that = const_cast<QMacSettingsPrivate *>(this); + QString impossibleKey(QLatin1String("qt_internal/")); + + QSettings::Status oldStatus = that->status; + that->status = QSettings::NoError; + + that->set(impossibleKey, QVariant()); + that->sync(); + bool writable = (status == QSettings::NoError) && that->get(impossibleKey, 0); + that->remove(impossibleKey); + that->sync(); + + that->status = oldStatus; + return writable; +} + +QString QMacSettingsPrivate::fileName() const +{ + QString result; + if ((spec & F_System) == 0) + result = QDir::homePath(); + result += QLatin1String("/Library/Preferences/"); + result += QCFString::toQString(domains[0].applicationOrSuiteId); + result += QLatin1String(".plist"); + return result; +} + +QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, + QSettings::Scope scope, + const QString &organization, + const QString &application) +{ + if (format == QSettings::NativeFormat) { + return new QMacSettingsPrivate(scope, organization, application); + } else { + return new QConfFileSettingsPrivate(format, scope, organization, application); + } +} + +static QCFType<CFURLRef> urlFromFileName(const QString &fileName) +{ + return CFURLCreateWithFileSystemPath(kCFAllocatorDefault, QCFString(fileName), + kCFURLPOSIXPathStyle, false); +} + +bool QConfFileSettingsPrivate::readPlistFile(const QString &fileName, ParsedSettingsMap *map) const +{ + QCFType<CFDataRef> resource; + SInt32 code; + if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, urlFromFileName(fileName), + &resource, 0, 0, &code)) + return false; + + QCFString errorStr; + QCFType<CFPropertyListRef> propertyList = + CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resource, kCFPropertyListImmutable, + &errorStr); + + if (!propertyList) + return true; + if (CFGetTypeID(propertyList) != CFDictionaryGetTypeID()) + return false; + + CFDictionaryRef cfdict = + static_cast<CFDictionaryRef>(static_cast<CFPropertyListRef>(propertyList)); + int size = (int)CFDictionaryGetCount(cfdict); + QVarLengthArray<CFPropertyListRef> keys(size); + QVarLengthArray<CFPropertyListRef> values(size); + CFDictionaryGetKeysAndValues(cfdict, keys.data(), values.data()); + + for (int i = 0; i < size; ++i) { + QString key = qtKey(static_cast<CFStringRef>(keys[i])); + map->insert(QSettingsKey(key, Qt::CaseSensitive), qtValue(values[i])); + } + return true; +} + +bool QConfFileSettingsPrivate::writePlistFile(const QString &fileName, + const ParsedSettingsMap &map) const +{ + QVarLengthArray<QCFType<CFStringRef> > cfkeys(map.size()); + QVarLengthArray<QCFType<CFPropertyListRef> > cfvalues(map.size()); + int i = 0; + ParsedSettingsMap::const_iterator j; + for (j = map.constBegin(); j != map.constEnd(); ++j) { + cfkeys[i] = macKey(j.key()); + cfvalues[i] = macValue(j.value()); + ++i; + } + + QCFType<CFDictionaryRef> propertyList = + CFDictionaryCreate(kCFAllocatorDefault, + reinterpret_cast<const void **>(cfkeys.data()), + reinterpret_cast<const void **>(cfvalues.data()), + CFIndex(map.size()), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + QCFType<CFDataRef> xmlData = CFPropertyListCreateXMLData(kCFAllocatorDefault, propertyList); + + SInt32 code; + return CFURLWriteDataAndPropertiesToResource(urlFromFileName(fileName), xmlData, 0, &code); +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qsettings_p.h b/src/corelib/io/qsettings_p.h new file mode 100644 index 0000000000..ec5b6bbe1b --- /dev/null +++ b/src/corelib/io/qsettings_p.h @@ -0,0 +1,316 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSETTINGS_P_H +#define QSETTINGS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qdatetime.h" +#include "QtCore/qmap.h" +#include "QtCore/qmutex.h" +#include "QtCore/qiodevice.h" +#include "QtCore/qstack.h" +#include "QtCore/qstringlist.h" +#ifndef QT_NO_QOBJECT +#include "private/qobject_p.h" +#endif +#include "private/qscopedpointer_p.h" + +#ifdef Q_OS_WIN +#include "QtCore/qt_windows.h" +#endif + +QT_BEGIN_NAMESPACE + +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) +#define QT_QSETTINGS_ALWAYS_CASE_SENSITIVE_AND_FORGET_ORIGINAL_KEY_ORDER +#endif + +// used in testing framework +#define QSETTINGS_P_H_VERSION 3 + +#ifdef QT_QSETTINGS_ALWAYS_CASE_SENSITIVE_AND_FORGET_ORIGINAL_KEY_ORDER +static const Qt::CaseSensitivity IniCaseSensitivity = Qt::CaseSensitive; + +class QSettingsKey : public QString +{ +public: + inline QSettingsKey(const QString &key, Qt::CaseSensitivity cs, int /* position */ = -1) + : QString(key) { Q_ASSERT(cs == Qt::CaseSensitive); Q_UNUSED(cs); } + + inline QString originalCaseKey() const { return *this; } + inline int originalKeyPosition() const { return -1; } +}; +#else +static const Qt::CaseSensitivity IniCaseSensitivity = Qt::CaseInsensitive; + +class QSettingsKey : public QString +{ +public: + inline QSettingsKey(const QString &key, Qt::CaseSensitivity cs, int position = -1) + : QString(key), theOriginalKey(key), theOriginalKeyPosition(position) + { + if (cs == Qt::CaseInsensitive) + QString::operator=(toLower()); + } + + inline QString originalCaseKey() const { return theOriginalKey; } + inline int originalKeyPosition() const { return theOriginalKeyPosition; } + +private: + QString theOriginalKey; + int theOriginalKeyPosition; +}; +#endif + +typedef QMap<QSettingsKey, QByteArray> UnparsedSettingsMap; +typedef QMap<QSettingsKey, QVariant> ParsedSettingsMap; + +class QSettingsGroup +{ +public: + inline QSettingsGroup() + : num(-1), maxNum(-1) {} + inline QSettingsGroup(const QString &s) + : str(s), num(-1), maxNum(-1) {} + inline QSettingsGroup(const QString &s, bool guessArraySize) + : str(s), num(0), maxNum(guessArraySize ? 0 : -1) {} + + inline QString name() const { return str; } + inline QString toString() const; + inline bool isArray() const { return num != -1; } + inline int arraySizeGuess() const { return maxNum; } + inline void setArrayIndex(int i) + { num = i + 1; if (maxNum != -1 && num > maxNum) maxNum = num; } + + QString str; + int num; + int maxNum; +}; + +inline QString QSettingsGroup::toString() const +{ + QString result; + result = str; + if (num > 0) { + result += QLatin1Char('/'); + result += QString::number(num); + } + return result; +} + +class Q_AUTOTEST_EXPORT QConfFile +{ +public: + ~QConfFile(); + + ParsedSettingsMap mergedKeyMap() const; + bool isWritable() const; + + static QConfFile *fromName(const QString &name, bool _userPerms); + static void clearCache(); + + QString name; + QDateTime timeStamp; + qint64 size; + UnparsedSettingsMap unparsedIniSections; + ParsedSettingsMap originalKeys; + ParsedSettingsMap addedKeys; + ParsedSettingsMap removedKeys; + QAtomicInt ref; + QMutex mutex; + bool userPerms; + +private: +#ifdef Q_DISABLE_COPY + QConfFile(const QConfFile &); + QConfFile &operator=(const QConfFile &); +#endif + QConfFile(const QString &name, bool _userPerms); + + friend class QConfFile_createsItself; // silences compiler warning +}; + +class Q_AUTOTEST_EXPORT QSettingsPrivate +#ifndef QT_NO_QOBJECT + : public QObjectPrivate +#endif +{ +#ifdef QT_NO_QOBJECT + QSettings *q_ptr; +#endif + Q_DECLARE_PUBLIC(QSettings) + +public: + QSettingsPrivate(QSettings::Format format); + QSettingsPrivate(QSettings::Format format, QSettings::Scope scope, + const QString &organization, const QString &application); + virtual ~QSettingsPrivate(); + + virtual void remove(const QString &key) = 0; + virtual void set(const QString &key, const QVariant &value) = 0; + virtual bool get(const QString &key, QVariant *value) const = 0; + + enum ChildSpec { AllKeys, ChildKeys, ChildGroups }; + virtual QStringList children(const QString &prefix, ChildSpec spec) const = 0; + + virtual void clear() = 0; + virtual void sync() = 0; + virtual void flush() = 0; + virtual bool isWritable() const = 0; + virtual QString fileName() const = 0; + + QString actualKey(const QString &key) const; + void beginGroupOrArray(const QSettingsGroup &group); + void setStatus(QSettings::Status status) const; + void requestUpdate(); + void update(); + + static QString normalizedKey(const QString &key); + static QSettingsPrivate *create(QSettings::Format format, QSettings::Scope scope, + const QString &organization, const QString &application); + static QSettingsPrivate *create(const QString &fileName, QSettings::Format format); + + static void processChild(QString key, ChildSpec spec, QMap<QString, QString> &result); + + // Variant streaming functions + static QStringList variantListToStringList(const QVariantList &l); + static QVariant stringListToVariantList(const QStringList &l); + + // parser functions + static QString variantToString(const QVariant &v); + static QVariant stringToVariant(const QString &s); + static void iniEscapedKey(const QString &key, QByteArray &result); + static bool iniUnescapedKey(const QByteArray &key, int from, int to, QString &result); + static void iniEscapedString(const QString &str, QByteArray &result, QTextCodec *codec); + static void iniEscapedStringList(const QStringList &strs, QByteArray &result, QTextCodec *codec); + static bool iniUnescapedStringList(const QByteArray &str, int from, int to, + QString &stringResult, QStringList &stringListResult, + QTextCodec *codec); + static QStringList splitArgs(const QString &s, int idx); + + /* + The numeric values of these enums define their search order. For example, + F_User | F_Organization is searched before F_System | F_Application, + because their values are respectively 1 and 2. + */ + enum { + F_Application = 0x0, + F_Organization = 0x1, + F_User = 0x0, + F_System = 0x2, + NumConfFiles = 4 + }; + + QSettings::Format format; + QSettings::Scope scope; + QString organizationName; + QString applicationName; + QTextCodec *iniCodec; + +protected: + QStack<QSettingsGroup> groupStack; + QString groupPrefix; + int spec; + bool fallbacks; + bool pendingChanges; + mutable QSettings::Status status; +}; + +class QConfFileSettingsPrivate : public QSettingsPrivate +{ +public: + QConfFileSettingsPrivate(QSettings::Format format, QSettings::Scope scope, + const QString &organization, const QString &application); + QConfFileSettingsPrivate(const QString &fileName, QSettings::Format format); + ~QConfFileSettingsPrivate(); + + void remove(const QString &key); + void set(const QString &key, const QVariant &value); + bool get(const QString &key, QVariant *value) const; + + QStringList children(const QString &prefix, ChildSpec spec) const; + + void clear(); + void sync(); + void flush(); + bool isWritable() const; + QString fileName() const; + + static bool readIniFile(const QByteArray &data, UnparsedSettingsMap *unparsedIniSections); + static bool readIniSection(const QSettingsKey §ion, const QByteArray &data, + ParsedSettingsMap *settingsMap, QTextCodec *codec); + static bool readIniLine(const QByteArray &data, int &dataPos, int &lineStart, int &lineLen, + int &equalsPos); + +private: + void initFormat(); + void initAccess(); + void syncConfFile(int confFileNo); + bool writeIniFile(QIODevice &device, const ParsedSettingsMap &map); +#ifdef Q_OS_MAC + bool readPlistFile(const QString &fileName, ParsedSettingsMap *map) const; + bool writePlistFile(const QString &fileName, const ParsedSettingsMap &map) const; +#endif + void ensureAllSectionsParsed(QConfFile *confFile) const; + void ensureSectionParsed(QConfFile *confFile, const QSettingsKey &key) const; + + QScopedSharedPointer<QConfFile> confFiles[NumConfFiles]; + QSettings::ReadFunc readFunc; + QSettings::WriteFunc writeFunc; + QString extension; + Qt::CaseSensitivity caseSensitivity; + int nextPosition; +}; + +QT_END_NAMESPACE + +#endif // QSETTINGS_P_H diff --git a/src/corelib/io/qsettings_win.cpp b/src/corelib/io/qsettings_win.cpp new file mode 100644 index 0000000000..3ef919a5ff --- /dev/null +++ b/src/corelib/io/qsettings_win.cpp @@ -0,0 +1,847 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsettings.h" + +#ifndef QT_NO_SETTINGS + +#include "qsettings_p.h" +#include "qvector.h" +#include "qmap.h" +#include "qt_windows.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +/* Keys are stored in QStrings. If the variable name starts with 'u', this is a "user" + key, ie. "foo/bar/alpha/beta". If the variable name starts with 'r', this is a "registry" + key, ie. "\foo\bar\alpha\beta". */ + +/******************************************************************************* +** Some convenience functions +*/ + +/* + We don't use KEY_ALL_ACCESS because it gives more rights than what we + need. See task 199061. + */ +static const REGSAM registryPermissions = KEY_READ | KEY_WRITE; + +static QString keyPath(const QString &rKey) +{ + int idx = rKey.lastIndexOf(QLatin1Char('\\')); + if (idx == -1) + return QString(); + return rKey.left(idx + 1); +} + +static QString keyName(const QString &rKey) +{ + int idx = rKey.lastIndexOf(QLatin1Char('\\')); + + QString res; + if (idx == -1) + res = rKey; + else + res = rKey.mid(idx + 1); + + if (res == QLatin1String("Default") || res == QLatin1String(".")) + res = QLatin1String(""); + + return res; +} + +static QString escapedKey(QString uKey) +{ + QChar *data = uKey.data(); + int l = uKey.length(); + for (int i = 0; i < l; ++i) { + ushort &ucs = data[i].unicode(); + if (ucs == '\\') + ucs = '/'; + else if (ucs == '/') + ucs = '\\'; + } + return uKey; +} + +static QString unescapedKey(QString rKey) +{ + return escapedKey(rKey); +} + +typedef QMap<QString, QString> NameSet; + +static void mergeKeySets(NameSet *dest, const NameSet &src) +{ + NameSet::const_iterator it = src.constBegin(); + for (; it != src.constEnd(); ++it) + dest->insert(unescapedKey(it.key()), QString()); +} + +static void mergeKeySets(NameSet *dest, const QStringList &src) +{ + QStringList::const_iterator it = src.constBegin(); + for (; it != src.constEnd(); ++it) + dest->insert(unescapedKey(*it), QString()); +} + +/******************************************************************************* +** Wrappers for the insane windows registry API +*/ + +static QString errorCodeToString(DWORD errorCode) +{ + wchar_t *data = 0; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, errorCode, 0, data, 0, 0); + QString result = QString::fromWCharArray(data); + + if (data != 0) + LocalFree(data); + + if (result.endsWith(QLatin1Char('\n'))) + result.truncate(result.length() - 1); + + return result; +} + +// Open a key with the specified perms +static HKEY openKey(HKEY parentHandle, REGSAM perms, const QString &rSubKey) +{ + HKEY resultHandle = 0; + LONG res = RegOpenKeyEx(parentHandle, reinterpret_cast<const wchar_t *>(rSubKey.utf16()), + 0, perms, &resultHandle); + + if (res == ERROR_SUCCESS) + return resultHandle; + + return 0; +} + +// Open a key with the specified perms, create it if it does not exist +static HKEY createOrOpenKey(HKEY parentHandle, REGSAM perms, const QString &rSubKey) +{ + // try to open it + HKEY resultHandle = openKey(parentHandle, perms, rSubKey); + if (resultHandle != 0) + return resultHandle; + + // try to create it + LONG res = RegCreateKeyEx(parentHandle, reinterpret_cast<const wchar_t *>(rSubKey.utf16()), 0, 0, + REG_OPTION_NON_VOLATILE, perms, 0, &resultHandle, 0); + + if (res == ERROR_SUCCESS) + return resultHandle; + + //qWarning("QSettings: Failed to create subkey \"%s\": %s", + // rSubKey.toLatin1().data(), errorCodeToString(res).toLatin1().data()); + + return 0; +} + +// Open or create a key in read-write mode if possible, otherwise read-only +static HKEY createOrOpenKey(HKEY parentHandle, const QString &rSubKey, bool *readOnly) +{ + // try to open or create it read/write + HKEY resultHandle = createOrOpenKey(parentHandle, registryPermissions, rSubKey); + if (resultHandle != 0) { + if (readOnly != 0) + *readOnly = false; + return resultHandle; + } + + // try to open or create it read/only + resultHandle = createOrOpenKey(parentHandle, KEY_READ, rSubKey); + if (resultHandle != 0) { + if (readOnly != 0) + *readOnly = true; + return resultHandle; + } + return 0; +} + +static QStringList childKeysOrGroups(HKEY parentHandle, QSettingsPrivate::ChildSpec spec) +{ + QStringList result; + DWORD numKeys; + DWORD maxKeySize; + DWORD numSubgroups; + DWORD maxSubgroupSize; + + // Find the number of keys and subgroups, as well as the max of their lengths. + LONG res = RegQueryInfoKey(parentHandle, 0, 0, 0, &numSubgroups, &maxSubgroupSize, 0, + &numKeys, &maxKeySize, 0, 0, 0); + + if (res != ERROR_SUCCESS) { + qWarning("QSettings: RegQueryInfoKey() failed: %s", errorCodeToString(res).toLatin1().data()); + return result; + } + + ++maxSubgroupSize; + ++maxKeySize; + + int n; + int m; + if (spec == QSettingsPrivate::ChildKeys) { + n = numKeys; + m = maxKeySize; + } else { + n = numSubgroups; + m = maxSubgroupSize; + } + + /* The size does not include the terminating null character. */ + ++m; + + // Get the list + QByteArray buff(m * sizeof(wchar_t), 0); + for (int i = 0; i < n; ++i) { + QString item; + DWORD l = buff.size() / sizeof(wchar_t); + if (spec == QSettingsPrivate::ChildKeys) { + res = RegEnumValue(parentHandle, i, reinterpret_cast<wchar_t *>(buff.data()), &l, 0, 0, 0, 0); + } else { + res = RegEnumKeyEx(parentHandle, i, reinterpret_cast<wchar_t *>(buff.data()), &l, 0, 0, 0, 0); + } + if (res == ERROR_SUCCESS) + item = QString::fromWCharArray((const wchar_t *)buff.constData(), l); + + if (res != ERROR_SUCCESS) { + qWarning("QSettings: RegEnumValue failed: %s", errorCodeToString(res).toLatin1().data()); + continue; + } + if (item.isEmpty()) + item = QLatin1String("."); + result.append(item); + } + return result; +} + +static void allKeys(HKEY parentHandle, const QString &rSubKey, NameSet *result) +{ + HKEY handle = openKey(parentHandle, KEY_READ, rSubKey); + if (handle == 0) + return; + + QStringList childKeys = childKeysOrGroups(handle, QSettingsPrivate::ChildKeys); + QStringList childGroups = childKeysOrGroups(handle, QSettingsPrivate::ChildGroups); + RegCloseKey(handle); + + for (int i = 0; i < childKeys.size(); ++i) { + QString s = rSubKey; + if (!s.isEmpty()) + s += QLatin1Char('\\'); + s += childKeys.at(i); + result->insert(s, QString()); + } + + for (int i = 0; i < childGroups.size(); ++i) { + QString s = rSubKey; + if (!s.isEmpty()) + s += QLatin1Char('\\'); + s += childGroups.at(i); + allKeys(parentHandle, s, result); + } +} + +static void deleteChildGroups(HKEY parentHandle) +{ + QStringList childGroups = childKeysOrGroups(parentHandle, QSettingsPrivate::ChildGroups); + + for (int i = 0; i < childGroups.size(); ++i) { + QString group = childGroups.at(i); + + // delete subgroups in group + HKEY childGroupHandle = openKey(parentHandle, registryPermissions, group); + if (childGroupHandle == 0) + continue; + deleteChildGroups(childGroupHandle); + RegCloseKey(childGroupHandle); + + // delete group itself + LONG res = RegDeleteKey(parentHandle, reinterpret_cast<const wchar_t *>(group.utf16())); + if (res != ERROR_SUCCESS) { + qWarning("QSettings: RegDeleteKey failed on subkey \"%s\": %s", + group.toLatin1().data(), errorCodeToString(res).toLatin1().data()); + return; + } + } +} + +/******************************************************************************* +** class RegistryKey +*/ + +class RegistryKey +{ +public: + RegistryKey(HKEY parent_handle = 0, const QString &key = QString(), bool read_only = true); + QString key() const; + HKEY handle() const; + HKEY parentHandle() const; + bool readOnly() const; + void close(); +private: + HKEY m_parent_handle; + mutable HKEY m_handle; + QString m_key; + mutable bool m_read_only; +}; + +RegistryKey::RegistryKey(HKEY parent_handle, const QString &key, bool read_only) +{ + m_parent_handle = parent_handle; + m_handle = 0; + m_read_only = read_only; + m_key = key; +} + +QString RegistryKey::key() const +{ + return m_key; +} + +HKEY RegistryKey::handle() const +{ + if (m_handle != 0) + return m_handle; + + if (m_read_only) + m_handle = openKey(m_parent_handle, KEY_READ, m_key); + else + m_handle = createOrOpenKey(m_parent_handle, m_key, &m_read_only); + + return m_handle; +} + +HKEY RegistryKey::parentHandle() const +{ + return m_parent_handle; +} + +bool RegistryKey::readOnly() const +{ + return m_read_only; +} + +void RegistryKey::close() +{ + if (m_handle != 0) + RegCloseKey(m_handle); + m_handle = 0; +} + +typedef QVector<RegistryKey> RegistryKeyList; + +/******************************************************************************* +** class QWinSettingsPrivate +*/ + +class QWinSettingsPrivate : public QSettingsPrivate +{ +public: + QWinSettingsPrivate(QSettings::Scope scope, const QString &organization, + const QString &application); + QWinSettingsPrivate(QString rKey); + ~QWinSettingsPrivate(); + + void remove(const QString &uKey); + void set(const QString &uKey, const QVariant &value); + bool get(const QString &uKey, QVariant *value) const; + QStringList children(const QString &uKey, ChildSpec spec) const; + void clear(); + void sync(); + void flush(); + bool isWritable() const; + HKEY writeHandle() const; + bool readKey(HKEY parentHandle, const QString &rSubKey, QVariant *value) const; + QString fileName() const; + +private: + RegistryKeyList regList; // list of registry locations to search for keys + bool deleteWriteHandleOnExit; +}; + +QWinSettingsPrivate::QWinSettingsPrivate(QSettings::Scope scope, const QString &organization, + const QString &application) + : QSettingsPrivate(QSettings::NativeFormat, scope, organization, application) +{ + deleteWriteHandleOnExit = false; + + if (!organization.isEmpty()) { + QString prefix = QLatin1String("Software\\") + organization; + QString orgPrefix = prefix + QLatin1String("\\OrganizationDefaults"); + QString appPrefix = prefix + QLatin1Char('\\') + application; + + if (scope == QSettings::UserScope) { + if (!application.isEmpty()) + regList.append(RegistryKey(HKEY_CURRENT_USER, appPrefix, !regList.isEmpty())); + + regList.append(RegistryKey(HKEY_CURRENT_USER, orgPrefix, !regList.isEmpty())); + } + + if (!application.isEmpty()) + regList.append(RegistryKey(HKEY_LOCAL_MACHINE, appPrefix, !regList.isEmpty())); + + regList.append(RegistryKey(HKEY_LOCAL_MACHINE, orgPrefix, !regList.isEmpty())); + } + + if (regList.isEmpty()) + setStatus(QSettings::AccessError); +} + +QWinSettingsPrivate::QWinSettingsPrivate(QString rPath) + : QSettingsPrivate(QSettings::NativeFormat) +{ + deleteWriteHandleOnExit = false; + + if (rPath.startsWith(QLatin1String("\\"))) + rPath = rPath.mid(1); + + if (rPath.startsWith(QLatin1String("HKEY_CURRENT_USER\\"))) + regList.append(RegistryKey(HKEY_CURRENT_USER, rPath.mid(18), false)); + else if (rPath == QLatin1String("HKEY_CURRENT_USER")) + regList.append(RegistryKey(HKEY_CURRENT_USER, QString(), false)); + else if (rPath.startsWith(QLatin1String("HKEY_LOCAL_MACHINE\\"))) + regList.append(RegistryKey(HKEY_LOCAL_MACHINE, rPath.mid(19), false)); + else if (rPath == QLatin1String("HKEY_LOCAL_MACHINE")) + regList.append(RegistryKey(HKEY_LOCAL_MACHINE, QString(), false)); + else if (rPath.startsWith(QLatin1String("HKEY_CLASSES_ROOT\\"))) + regList.append(RegistryKey(HKEY_CLASSES_ROOT, rPath.mid(18), false)); + else if (rPath == QLatin1String("HKEY_CLASSES_ROOT")) + regList.append(RegistryKey(HKEY_CLASSES_ROOT, QString(), false)); + else if (rPath.startsWith(QLatin1String("HKEY_USERS\\"))) + regList.append(RegistryKey(HKEY_USERS, rPath.mid(11), false)); + else if (rPath == QLatin1String(QLatin1String("HKEY_USERS"))) + regList.append(RegistryKey(HKEY_USERS, QString(), false)); + else + regList.append(RegistryKey(HKEY_LOCAL_MACHINE, rPath, false)); +} + +bool QWinSettingsPrivate::readKey(HKEY parentHandle, const QString &rSubKey, QVariant *value) const +{ + QString rSubkeyName = keyName(rSubKey); + QString rSubkeyPath = keyPath(rSubKey); + + // open a handle on the subkey + HKEY handle = openKey(parentHandle, KEY_READ, rSubkeyPath); + if (handle == 0) + return false; + + // get the size and type of the value + DWORD dataType; + DWORD dataSize; + LONG res = RegQueryValueEx(handle, reinterpret_cast<const wchar_t *>(rSubkeyName.utf16()), 0, &dataType, 0, &dataSize); + if (res != ERROR_SUCCESS) { + RegCloseKey(handle); + return false; + } + + // get the value + QByteArray data(dataSize, 0); + res = RegQueryValueEx(handle, reinterpret_cast<const wchar_t *>(rSubkeyName.utf16()), 0, 0, + reinterpret_cast<unsigned char*>(data.data()), &dataSize); + if (res != ERROR_SUCCESS) { + RegCloseKey(handle); + return false; + } + + switch (dataType) { + case REG_EXPAND_SZ: + case REG_SZ: { + QString s; + if (dataSize) { + s = QString::fromWCharArray(((const wchar_t *)data.constData())); + } + if (value != 0) + *value = stringToVariant(s); + break; + } + + case REG_MULTI_SZ: { + QStringList l; + if (dataSize) { + int i = 0; + for (;;) { + QString s = QString::fromWCharArray((const wchar_t *)data.constData() + i); + i += s.length() + 1; + + if (s.isEmpty()) + break; + l.append(s); + } + } + if (value != 0) + *value = stringListToVariantList(l); + break; + } + + case REG_NONE: + case REG_BINARY: { + QString s; + if (dataSize) { + s = QString::fromWCharArray((const wchar_t *)data.constData(), data.size() / 2); + } + if (value != 0) + *value = stringToVariant(s); + break; + } + + case REG_DWORD_BIG_ENDIAN: + case REG_DWORD: { + Q_ASSERT(data.size() == sizeof(int)); + int i; + memcpy((char*)&i, data.constData(), sizeof(int)); + if (value != 0) + *value = i; + break; + } + + case REG_QWORD: { + Q_ASSERT(data.size() == sizeof(qint64)); + qint64 i; + memcpy((char*)&i, data.constData(), sizeof(qint64)); + if (value != 0) + *value = i; + break; + } + + default: + qWarning("QSettings: Unknown data %d type in Windows registry", static_cast<int>(dataType)); + if (value != 0) + *value = QVariant(); + break; + } + + RegCloseKey(handle); + return true; +} + +HKEY QWinSettingsPrivate::writeHandle() const +{ + if (regList.isEmpty()) + return 0; + const RegistryKey &key = regList.at(0); + if (key.handle() == 0 || key.readOnly()) + return 0; + return key.handle(); +} + +QWinSettingsPrivate::~QWinSettingsPrivate() +{ + if (deleteWriteHandleOnExit && writeHandle() != 0) { +#if defined(Q_OS_WINCE) + remove(regList.at(0).key()); +#else + QString emptyKey; + DWORD res = RegDeleteKey(writeHandle(), reinterpret_cast<const wchar_t *>(emptyKey.utf16())); + if (res != ERROR_SUCCESS) { + qWarning("QSettings: Failed to delete key \"%s\": %s", + regList.at(0).key().toLatin1().data(), errorCodeToString(res).toLatin1().data()); + } +#endif + } + + for (int i = 0; i < regList.size(); ++i) + regList[i].close(); +} + +void QWinSettingsPrivate::remove(const QString &uKey) +{ + if (writeHandle() == 0) { + setStatus(QSettings::AccessError); + return; + } + + QString rKey = escapedKey(uKey); + + // try to delete value bar in key foo + LONG res; + HKEY handle = openKey(writeHandle(), registryPermissions, keyPath(rKey)); + if (handle != 0) { + res = RegDeleteValue(handle, reinterpret_cast<const wchar_t *>(keyName(rKey).utf16())); + RegCloseKey(handle); + } + + // try to delete key foo/bar and all subkeys + handle = openKey(writeHandle(), registryPermissions, rKey); + if (handle != 0) { + deleteChildGroups(handle); + + if (rKey.isEmpty()) { + QStringList childKeys = childKeysOrGroups(handle, QSettingsPrivate::ChildKeys); + + for (int i = 0; i < childKeys.size(); ++i) { + QString group = childKeys.at(i); + + LONG res = RegDeleteValue(handle, reinterpret_cast<const wchar_t *>(group.utf16())); + if (res != ERROR_SUCCESS) { + qWarning("QSettings: RegDeleteValue failed on subkey \"%s\": %s", + group.toLatin1().data(), errorCodeToString(res).toLatin1().data()); + } + } + } else { +#if defined(Q_OS_WINCE) + // For WinCE always Close the handle first. + RegCloseKey(handle); +#endif + res = RegDeleteKey(writeHandle(), reinterpret_cast<const wchar_t *>(rKey.utf16())); + + if (res != ERROR_SUCCESS) { + qWarning("QSettings: RegDeleteKey failed on key \"%s\": %s", + rKey.toLatin1().data(), errorCodeToString(res).toLatin1().data()); + } + } + RegCloseKey(handle); + } +} + +static bool stringContainsNullChar(const QString &s) +{ + for (int i = 0; i < s.length(); ++i) { + if (s.at(i).unicode() == 0) + return true; + } + return false; +} + +void QWinSettingsPrivate::set(const QString &uKey, const QVariant &value) +{ + if (writeHandle() == 0) { + setStatus(QSettings::AccessError); + return; + } + + QString rKey = escapedKey(uKey); + + HKEY handle = createOrOpenKey(writeHandle(), registryPermissions, keyPath(rKey)); + if (handle == 0) { + setStatus(QSettings::AccessError); + return; + } + + DWORD type; + QByteArray regValueBuff; + + // Determine the type + switch (value.type()) { + case QVariant::List: + case QVariant::StringList: { + // If none of the elements contains '\0', we can use REG_MULTI_SZ, the + // native registry string list type. Otherwise we use REG_BINARY. + type = REG_MULTI_SZ; + QStringList l = variantListToStringList(value.toList()); + QStringList::const_iterator it = l.constBegin(); + for (; it != l.constEnd(); ++it) { + if ((*it).length() == 0 || stringContainsNullChar(*it)) { + type = REG_BINARY; + break; + } + } + + if (type == REG_BINARY) { + QString s = variantToString(value); + regValueBuff = QByteArray((const char*)s.utf16(), s.length() * 2); + } else { + QStringList::const_iterator it = l.constBegin(); + for (; it != l.constEnd(); ++it) { + const QString &s = *it; + regValueBuff += QByteArray((const char*)s.utf16(), (s.length() + 1) * 2); + } + regValueBuff.append((char)0); + regValueBuff.append((char)0); + } + break; + } + + case QVariant::Int: + case QVariant::UInt: { + type = REG_DWORD; + qint32 i = value.toInt(); + regValueBuff = QByteArray((const char*)&i, sizeof(qint32)); + break; + } + + case QVariant::LongLong: + case QVariant::ULongLong: { + type = REG_QWORD; + qint64 i = value.toLongLong(); + regValueBuff = QByteArray((const char*)&i, sizeof(qint64)); + break; + } + + case QVariant::ByteArray: + // fallthrough intended + + default: { + // If the string does not contain '\0', we can use REG_SZ, the native registry + // string type. Otherwise we use REG_BINARY. + QString s = variantToString(value); + type = stringContainsNullChar(s) ? REG_BINARY : REG_SZ; + if (type == REG_BINARY) { + regValueBuff = QByteArray((const char*)s.utf16(), s.length() * 2); + } else { + regValueBuff = QByteArray((const char*)s.utf16(), (s.length() + 1) * 2); + } + break; + } + } + + // set the value + LONG res = RegSetValueEx(handle, reinterpret_cast<const wchar_t *>(keyName(rKey).utf16()), 0, type, + reinterpret_cast<const unsigned char*>(regValueBuff.constData()), + regValueBuff.size()); + + if (res == ERROR_SUCCESS) { + deleteWriteHandleOnExit = false; + } else { + qWarning("QSettings: failed to set subkey \"%s\": %s", + rKey.toLatin1().data(), errorCodeToString(res).toLatin1().data()); + setStatus(QSettings::AccessError); + } + + RegCloseKey(handle); +} + +bool QWinSettingsPrivate::get(const QString &uKey, QVariant *value) const +{ + QString rKey = escapedKey(uKey); + + for (int i = 0; i < regList.size(); ++i) { + HKEY handle = regList.at(i).handle(); + if (handle != 0 && readKey(handle, rKey, value)) + return true; + + if (!fallbacks) + return false; + } + + return false; +} + +QStringList QWinSettingsPrivate::children(const QString &uKey, ChildSpec spec) const +{ + NameSet result; + QString rKey = escapedKey(uKey); + + for (int i = 0; i < regList.size(); ++i) { + HKEY parent_handle = regList.at(i).handle(); + if (parent_handle == 0) + continue; + HKEY handle = openKey(parent_handle, KEY_READ, rKey); + if (handle == 0) + continue; + + if (spec == AllKeys) { + NameSet keys; + allKeys(handle, QLatin1String(""), &keys); + mergeKeySets(&result, keys); + } else { // ChildGroups or ChildKeys + QStringList names = childKeysOrGroups(handle, spec); + mergeKeySets(&result, names); + } + + RegCloseKey(handle); + + if (!fallbacks) + return result.keys(); + } + + return result.keys(); +} + +void QWinSettingsPrivate::clear() +{ + remove(QString()); + deleteWriteHandleOnExit = true; +} + +void QWinSettingsPrivate::sync() +{ + RegFlushKey(writeHandle()); +} + +void QWinSettingsPrivate::flush() +{ + // Windows does this for us. +} + +QString QWinSettingsPrivate::fileName() const +{ + if (regList.isEmpty()) + return QString(); + + const RegistryKey &key = regList.at(0); + QString result; + if (key.parentHandle() == HKEY_CURRENT_USER) + result = QLatin1String("\\HKEY_CURRENT_USER\\"); + else + result = QLatin1String("\\HKEY_LOCAL_MACHINE\\"); + + return result + regList.at(0).key(); +} + +bool QWinSettingsPrivate::isWritable() const +{ + return writeHandle() != 0; +} + +QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope, + const QString &organization, const QString &application) +{ + if (format == QSettings::NativeFormat) { + return new QWinSettingsPrivate(scope, organization, application); + } else { + return new QConfFileSettingsPrivate(format, scope, organization, application); + } +} + +QSettingsPrivate *QSettingsPrivate::create(const QString &fileName, QSettings::Format format) +{ + if (format == QSettings::NativeFormat) { + return new QWinSettingsPrivate(fileName); + } else { + return new QConfFileSettingsPrivate(fileName, format); + } +} + +QT_END_NAMESPACE +#endif // QT_NO_SETTINGS diff --git a/src/corelib/io/qtemporaryfile.cpp b/src/corelib/io/qtemporaryfile.cpp new file mode 100644 index 0000000000..b5e5808145 --- /dev/null +++ b/src/corelib/io/qtemporaryfile.cpp @@ -0,0 +1,713 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtemporaryfile.h" + +#ifndef QT_NO_TEMPORARYFILE + +#include "qplatformdefs.h" +#include "qabstractfileengine.h" +#include "private/qfile_p.h" +#include "private/qabstractfileengine_p.h" +#include "private/qfsfileengine_p.h" + +#if !defined(Q_OS_WINCE) +# include <errno.h> +#endif + +#include <stdlib.h> +#include <time.h> +#include <ctype.h> + +#if defined(Q_OS_UNIX) +# include "private/qcore_unix_p.h" // overrides QT_OPEN +#endif + +#if defined(QT_BUILD_CORE_LIB) +#include "qcoreapplication.h" +#endif + +QT_BEGIN_NAMESPACE + +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*! + \internal + + Generates a unique file path and returns a native handle to the open file. + \a path is used as a template when generating unique paths, + \a placeholderStart and \a placeholderEnd delimit the sub-string that will + be randomized. + + Returns an open handle to the newly created file if successful, an invalid + handle otherwise. In both cases, the string in \a path will be changed and + contain the generated path name. +*/ +static int createFileFromTemplate(char *const path, + char *const placeholderStart, char *const placeholderEnd) +{ + Q_ASSERT(placeholderEnd > placeholderStart); + + // Initialize placeholder with random chars + PID. + { + char *rIter = placeholderEnd; + +#if defined(QT_BUILD_CORE_LIB) + qint64 pid = QCoreApplication::applicationPid(); + do { + *--rIter = (pid % 10) + '0'; + pid /= 10; + } while (rIter != placeholderStart && pid != 0); +#endif + + while (rIter != placeholderStart) { + char ch = char((qrand() & 0xffff) % (26 + 26)); + if (ch < 26) + *--rIter = ch + 'A'; + else + *--rIter = ch - 26 + 'a'; + } + } + + for (;;) { + // Atomically create file and obtain handle +#ifndef Q_OS_WIN + { + int fd = QT_OPEN(path, QT_OPEN_CREAT | O_EXCL | QT_OPEN_RDWR | QT_OPEN_LARGEFILE, 0600); + if (fd != -1) + return fd; + if (errno != EEXIST) + return -1; + } +#else + if (!QFileInfo(QLatin1String(path)).exists()) + return 1; +#endif + + /* tricky little algorwwithm for backward compatibility */ + for (char *iter = placeholderStart;;) { + // Character progression: [0-9] => 'a' ... 'z' => 'A' .. 'Z' + // String progression: "ZZaiC" => "aabiC" + if (*iter == 'Z') { + *iter++ = 'a'; + if (iter == placeholderEnd) + return -1; + } else { + if (isdigit(*iter)) + *iter = 'a'; + else if (*iter == 'z') /* inc from z to A */ + *iter = 'A'; + else { + ++*iter; + } + break; + } + } + } + /*NOTREACHED*/ +} + +//************* QTemporaryFileEngine +class QTemporaryFileEngine : public QFSFileEngine +{ + Q_DECLARE_PRIVATE(QFSFileEngine) +public: + QTemporaryFileEngine(const QString &file, bool fileIsTemplate = true) + : QFSFileEngine(), filePathIsTemplate(fileIsTemplate) + { + Q_D(QFSFileEngine); + d->fileEntry = QFileSystemEntry(file); + + if (!filePathIsTemplate) + QFSFileEngine::setFileName(file); + } + + ~QTemporaryFileEngine(); + + bool isReallyOpen(); + void setFileName(const QString &file); + void setFileTemplate(const QString &fileTemplate); + + bool open(QIODevice::OpenMode flags); + bool remove(); + bool rename(const QString &newName); + bool close(); + + bool filePathIsTemplate; +}; + +QTemporaryFileEngine::~QTemporaryFileEngine() +{ + QFSFileEngine::close(); +} + +bool QTemporaryFileEngine::isReallyOpen() +{ + Q_D(QFSFileEngine); + + if (!((0 == d->fh) && (-1 == d->fd) +#if defined (Q_OS_SYMBIAN) + && (0 == d->symbianFile.SubSessionHandle()) +#endif +#if defined Q_OS_WIN + && (INVALID_HANDLE_VALUE == d->fileHandle) +#endif + )) + return true; + + return false; + +} + +void QTemporaryFileEngine::setFileName(const QString &file) +{ + // Really close the file, so we don't leak + QFSFileEngine::close(); + QFSFileEngine::setFileName(file); +} + +void QTemporaryFileEngine::setFileTemplate(const QString &fileTemplate) +{ + Q_D(QFSFileEngine); + if (filePathIsTemplate) + d->fileEntry = QFileSystemEntry(fileTemplate); +} + +bool QTemporaryFileEngine::open(QIODevice::OpenMode openMode) +{ + Q_D(QFSFileEngine); + Q_ASSERT(!isReallyOpen()); + + openMode |= QIODevice::ReadWrite; + + if (!filePathIsTemplate) + return QFSFileEngine::open(openMode); + + QString qfilename = d->fileEntry.filePath(); + + // Find placeholder string. + uint phPos = qfilename.length(); + uint phLength = 0; + + while (phPos != 0) { + --phPos; + + if (qfilename[phPos] == QLatin1Char('X')) { + ++phLength; + continue; + } + + if (qfilename[phPos] == QLatin1Char('/') + || phLength >= 6) { + ++phPos; + break; + } + + phLength = 0; + } + + QStringRef prefix, suffix; + if (phLength < 6) { + qfilename += QLatin1Char('.'); + prefix = QStringRef(&qfilename); + phLength = 6; + } else { + prefix = qfilename.leftRef(phPos); + suffix = qfilename.midRef(phPos + phLength); + } + + QByteArray filename = prefix.toLocal8Bit(); + phPos = filename.length(); + if (suffix.isEmpty()) + filename.resize(phPos + phLength); + else + filename.insert(phPos + phLength, suffix.toLocal8Bit()); + + char *path = filename.data(); + +#ifndef Q_OS_WIN + int fd = createFileFromTemplate(path, path + phPos, path + phPos + phLength); + if (fd != -1) { + // First open the fd as an external file descriptor to + // initialize the engine properly. + if (QFSFileEngine::open(openMode, fd)) { + + // Allow the engine to close the handle even if it's "external". + d->closeFileHandle = true; + + // Restore the file names (open() resets them). + d->fileEntry = QFileSystemEntry(QString::fromLocal8Bit(path, filename.length())); //note that filename is NOT a native path + filePathIsTemplate = false; + return true; + } + + QT_CLOSE(fd); + } + setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, qt_error_string(errno)); + return false; +#else + if (createFileFromTemplate(path, path + phPos, path + phPos + phLength) == -1) { + return false; + } + + QString template_ = d->fileEntry.filePath(); + d->fileEntry = QFileSystemEntry(QString::fromLocal8Bit(path, filename.length())); + + if (QFSFileEngine::open(openMode)) { + filePathIsTemplate = false; + return true; + } + + d->fileEntry = QFileSystemEntry(template_); + return false; +#endif +} + +bool QTemporaryFileEngine::remove() +{ + Q_D(QFSFileEngine); + // Since the QTemporaryFileEngine::close() does not really close the file, + // we must explicitly call QFSFileEngine::close() before we remove it. + QFSFileEngine::close(); + if (QFSFileEngine::remove()) { + d->fileEntry.clear(); + return true; + } + return false; +} + +bool QTemporaryFileEngine::rename(const QString &newName) +{ + QFSFileEngine::close(); + return QFSFileEngine::rename(newName); +} + +bool QTemporaryFileEngine::close() +{ + // Don't close the file, just seek to the front. + seek(0); + setError(QFile::UnspecifiedError, QString()); + return true; +} + +//************* QTemporaryFilePrivate +class QTemporaryFilePrivate : public QFilePrivate +{ + Q_DECLARE_PUBLIC(QTemporaryFile) + +protected: + QTemporaryFilePrivate(); + ~QTemporaryFilePrivate(); + + bool autoRemove; + QString templateName; +}; + +QTemporaryFilePrivate::QTemporaryFilePrivate() : autoRemove(true) +{ +} + +QTemporaryFilePrivate::~QTemporaryFilePrivate() +{ +} + +//************* QTemporaryFile + +/*! + \class QTemporaryFile + \reentrant + \brief The QTemporaryFile class is an I/O device that operates on temporary files. + + \ingroup io + + + QTemporaryFile is used to create unique temporary files safely. + The file itself is created by calling open(). The name of the + temporary file is guaranteed to be unique (i.e., you are + guaranteed to not overwrite an existing file), and the file will + subsequently be removed upon destruction of the QTemporaryFile + object. This is an important technique that avoids data + corruption for applications that store data in temporary files. + The file name is either auto-generated, or created based on a + template, which is passed to QTemporaryFile's constructor. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtemporaryfile.cpp 0 + + Reopening a QTemporaryFile after calling close() is safe. For as long as + the QTemporaryFile object itself is not destroyed, the unique temporary + file will exist and be kept open internally by QTemporaryFile. + + The file name of the temporary file can be found by calling fileName(). + Note that this is only defined after the file is first opened; the function + returns an empty string before this. + + A temporary file will have some static part of the name and some + part that is calculated to be unique. The default filename \c + qt_temp will be placed into the temporary path as returned by + QDir::tempPath(). If you specify your own filename, a relative + file path will not be placed in the temporary directory by + default, but be relative to the current working directory. + + Specified filenames can contain the following template \c XXXXXX + (six upper case "X" characters), which will be replaced by the + auto-generated portion of the filename. Note that the template is + case sensitive. If the template is not present in the filename, + QTemporaryFile appends the generated part to the filename given. + + \sa QDir::tempPath(), QFile +*/ + +#ifdef QT_NO_QOBJECT +QTemporaryFile::QTemporaryFile() + : QFile(*new QTemporaryFilePrivate) +{ + Q_D(QTemporaryFile); + d->templateName = QDir::tempPath() + QLatin1String("/qt_temp.XXXXXX"); +} + +QTemporaryFile::QTemporaryFile(const QString &templateName) + : QFile(*new QTemporaryFilePrivate) +{ + Q_D(QTemporaryFile); + d->templateName = templateName; +} + +#else +/*! + Constructs a QTemporaryFile in QDir::tempPath(), using the file template + "qt_temp.XXXXXX". The file is stored in the system's temporary directory. + + \sa setFileTemplate(), QDir::tempPath() +*/ +QTemporaryFile::QTemporaryFile() + : QFile(*new QTemporaryFilePrivate, 0) +{ + Q_D(QTemporaryFile); + d->templateName = QDir::tempPath() + QLatin1String("/qt_temp.XXXXXX"); +} + +/*! + Constructs a QTemporaryFile with a template filename of \a + templateName. Upon opening the temporary file this will be used to create + a unique filename. + + If the \a templateName does not contain XXXXXX it will automatically be + appended and used as the dynamic portion of the filename. + + If \a templateName is a relative path, the path will be relative to the + current working directory. You can use QDir::tempPath() to construct \a + templateName if you want use the system's temporary directory. + + \sa open(), fileTemplate() +*/ +QTemporaryFile::QTemporaryFile(const QString &templateName) + : QFile(*new QTemporaryFilePrivate, 0) +{ + Q_D(QTemporaryFile); + d->templateName = templateName; +} + +/*! + Constructs a QTemporaryFile (with the given \a parent) in + QDir::tempPath(), using the file template "qt_temp.XXXXXX". + + \sa setFileTemplate() +*/ +QTemporaryFile::QTemporaryFile(QObject *parent) + : QFile(*new QTemporaryFilePrivate, parent) +{ + Q_D(QTemporaryFile); + d->templateName = QDir::tempPath() + QLatin1String("/qt_temp.XXXXXX"); +} + +/*! + Constructs a QTemporaryFile with a template filename of \a + templateName and the specified \a parent. + Upon opening the temporary file this will be used to + create a unique filename. + + If the \a templateName does not contain XXXXXX it will automatically be + appended and used as the dynamic portion of the filename. + + If \a templateName is a relative path, the path will be relative to the + current working directory. You can use QDir::tempPath() to construct \a + templateName if you want use the system's temporary directory. + + \sa open(), fileTemplate() +*/ +QTemporaryFile::QTemporaryFile(const QString &templateName, QObject *parent) + : QFile(*new QTemporaryFilePrivate, parent) +{ + Q_D(QTemporaryFile); + d->templateName = templateName; +} +#endif + +/*! + Destroys the temporary file object, the file is automatically + closed if necessary and if in auto remove mode it will + automatically delete the file. + + \sa autoRemove() +*/ +QTemporaryFile::~QTemporaryFile() +{ + Q_D(QTemporaryFile); + close(); + if (!d->fileName.isEmpty() && d->autoRemove) + remove(); +} + +/*! + \fn bool QTemporaryFile::open() + + A QTemporaryFile will always be opened in QIODevice::ReadWrite mode, + this allows easy access to the data in the file. This function will + return true upon success and will set the fileName() to the unique + filename used. + + \sa fileName() +*/ + +/*! + Returns true if the QTemporaryFile is in auto remove + mode. Auto-remove mode will automatically delete the filename from + disk upon destruction. This makes it very easy to create your + QTemporaryFile object on the stack, fill it with data, read from + it, and finally on function return it will automatically clean up + after itself. + + Auto-remove is on by default. + + \sa setAutoRemove(), remove() +*/ +bool QTemporaryFile::autoRemove() const +{ + Q_D(const QTemporaryFile); + return d->autoRemove; +} + +/*! + Sets the QTemporaryFile into auto-remove mode if \a b is true. + + Auto-remove is on by default. + + \sa autoRemove(), remove() +*/ +void QTemporaryFile::setAutoRemove(bool b) +{ + Q_D(QTemporaryFile); + d->autoRemove = b; +} + +/*! + Returns the complete unique filename backing the QTemporaryFile + object. This string is null before the QTemporaryFile is opened, + afterwards it will contain the fileTemplate() plus + additional characters to make it unique. + + \sa fileTemplate() +*/ + +QString QTemporaryFile::fileName() const +{ + Q_D(const QTemporaryFile); + if(d->fileName.isEmpty()) + return QString(); + return fileEngine()->fileName(QAbstractFileEngine::DefaultName); +} + +/*! + Returns the set file template. The default file template will be + called qt_temp and be placed in QDir::tempPath(). + + \sa setFileTemplate() +*/ +QString QTemporaryFile::fileTemplate() const +{ + Q_D(const QTemporaryFile); + return d->templateName; +} + +/*! + Sets the static portion of the file name to \a name. If the file + template ends in XXXXXX that will automatically be replaced with + the unique part of the filename, otherwise a filename will be + determined automatically based on the static portion specified. + + If \a name contains a relative file path, the path will be relative to the + current working directory. You can use QDir::tempPath() to construct \a + name if you want use the system's temporary directory. + + \sa fileTemplate() +*/ +void QTemporaryFile::setFileTemplate(const QString &name) +{ + Q_D(QTemporaryFile); + d->templateName = name; + if (d->fileEngine) + static_cast<QTemporaryFileEngine*>(d->fileEngine)->setFileTemplate(name); +} + +/*! + \fn QTemporaryFile *QTemporaryFile::createLocalFile(const QString &fileName) + \overload + + Works on the given \a fileName rather than an existing QFile + object. +*/ + + +/*! + If \a file is not on a local disk, a temporary file is created + on a local disk, \a file is copied into the temporary local file, + and a pointer to the temporary local file is returned. If \a file + is already on a local disk, a copy is not created and 0 is returned. +*/ +QTemporaryFile *QTemporaryFile::createLocalFile(QFile &file) +{ + if (QAbstractFileEngine *engine = file.fileEngine()) { + if(engine->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::LocalDiskFlag) + return 0; //local already + //cache + bool wasOpen = file.isOpen(); + qint64 old_off = 0; + if(wasOpen) + old_off = file.pos(); + else + file.open(QIODevice::ReadOnly); + //dump data + QTemporaryFile *ret = new QTemporaryFile; + ret->open(); + file.seek(0); + char buffer[1024]; + while(true) { + qint64 len = file.read(buffer, 1024); + if(len < 1) + break; + ret->write(buffer, len); + } + ret->seek(0); + //restore + if(wasOpen) + file.seek(old_off); + else + file.close(); + //done + return ret; + } + return 0; +} + +/*! + \internal +*/ + +QAbstractFileEngine *QTemporaryFile::fileEngine() const +{ + Q_D(const QTemporaryFile); + if(!d->fileEngine) { + if (d->fileName.isEmpty()) + d->fileEngine = new QTemporaryFileEngine(d->templateName); + else + d->fileEngine = new QTemporaryFileEngine(d->fileName, false); + } + return d->fileEngine; +} + +/*! + \reimp + + Creates a unique file name for the temporary file, and opens it. You can + get the unique name later by calling fileName(). The file is guaranteed to + have been created by this function (i.e., it has never existed before). +*/ +bool QTemporaryFile::open(OpenMode flags) +{ + Q_D(QTemporaryFile); + if (!d->fileName.isEmpty()) { + if (static_cast<QTemporaryFileEngine*>(fileEngine())->isReallyOpen()) { + setOpenMode(flags); + return true; + } + } + + if (QFile::open(flags)) { + d->fileName = d->fileEngine->fileName(QAbstractFileEngine::DefaultName); + return true; + } + return false; +} + +QT_END_NAMESPACE + +#endif // QT_NO_TEMPORARYFILE + + diff --git a/src/corelib/io/qtemporaryfile.h b/src/corelib/io/qtemporaryfile.h new file mode 100644 index 0000000000..23b41dcdce --- /dev/null +++ b/src/corelib/io/qtemporaryfile.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEMPORARYFILE_H +#define QTEMPORARYFILE_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qfile.h> + +#ifdef open +#error qtemporaryfile.h must be included before any header file that defines open +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +#ifndef QT_NO_TEMPORARYFILE + +class QTemporaryFilePrivate; + +class Q_CORE_EXPORT QTemporaryFile : public QFile +{ +#ifndef QT_NO_QOBJECT + Q_OBJECT +#endif + Q_DECLARE_PRIVATE(QTemporaryFile) + +public: + QTemporaryFile(); + explicit QTemporaryFile(const QString &templateName); +#ifndef QT_NO_QOBJECT + explicit QTemporaryFile(QObject *parent); + QTemporaryFile(const QString &templateName, QObject *parent); +#endif + ~QTemporaryFile(); + + bool autoRemove() const; + void setAutoRemove(bool b); + + // ### Hides open(flags) + bool open() { return open(QIODevice::ReadWrite); } + + QString fileName() const; + QString fileTemplate() const; + void setFileTemplate(const QString &name); + + inline static QTemporaryFile *createLocalFile(const QString &fileName) + { QFile file(fileName); return createLocalFile(file); } + static QTemporaryFile *createLocalFile(QFile &file); + + virtual QAbstractFileEngine *fileEngine() const; + +protected: + bool open(OpenMode flags); + +private: + friend class QFile; + Q_DISABLE_COPY(QTemporaryFile) +}; + +#endif // QT_NO_TEMPORARYFILE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEMPORARYFILE_H diff --git a/src/corelib/io/qtextstream.cpp b/src/corelib/io/qtextstream.cpp new file mode 100644 index 0000000000..a5837cbeb5 --- /dev/null +++ b/src/corelib/io/qtextstream.cpp @@ -0,0 +1,3413 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QTEXTSTREAM_DEBUG +static const int QTEXTSTREAM_BUFFERSIZE = 16384; + +/*! + \class QTextStream + + \brief The QTextStream class provides a convenient interface for + reading and writing text. + + \ingroup io + \ingroup string-processing + \reentrant + + QTextStream can operate on a QIODevice, a QByteArray or a + QString. Using QTextStream's streaming operators, you can + conveniently read and write words, lines and numbers. For + generating text, QTextStream supports formatting options for field + padding and alignment, and formatting of numbers. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 0 + + It's also common to use QTextStream to read console input and write + console output. QTextStream is locale aware, and will automatically decode + standard input using the correct codec. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 1 + + Note that you cannot use QTextStream::atEnd(), which returns true when you + have reached the end of the data stream, with stdin. The reason for this is + that as long as stdin doesn't give any input to the QTextStream, \c atEnd() + will return true even if the stdin is open and waiting for more characters. + + Besides using QTextStream's constructors, you can also set the + device or string QTextStream operates on by calling setDevice() or + setString(). You can seek to a position by calling seek(), and + atEnd() will return true when there is no data left to be read. If + you call flush(), QTextStream will empty all data from its write + buffer into the device and call flush() on the device. + + Internally, QTextStream uses a Unicode based buffer, and + QTextCodec is used by QTextStream to automatically support + different character sets. By default, QTextCodec::codecForLocale() + is used for reading and writing, but you can also set the codec by + calling setCodec(). Automatic Unicode detection is also + supported. When this feature is enabled (the default behavior), + QTextStream will detect the UTF-16 or the UTF-32 BOM (Byte Order Mark) and + switch to the appropriate UTF codec when reading. QTextStream + does not write a BOM by default, but you can enable this by calling + setGenerateByteOrderMark(true). When QTextStream operates on a QString + directly, the codec is disabled. + + There are three general ways to use QTextStream when reading text + files: + + \list + + \o Chunk by chunk, by calling readLine() or readAll(). + + \o Word by word. QTextStream supports streaming into QStrings, + QByteArrays and char* buffers. Words are delimited by space, and + leading white space is automatically skipped. + + \o Character by character, by streaming into QChar or char types. + This method is often used for convenient input handling when + parsing files, independent of character encoding and end-of-line + semantics. To skip white space, call skipWhiteSpace(). + + \endlist + + Since the text stream uses a buffer, you should not read from + the stream using the implementation of a superclass. For instance, + if you have a QFile and read from it directly using + QFile::readLine() instead of using the stream, the text stream's + internal position will be out of sync with the file's position. + + By default, when reading numbers from a stream of text, + QTextStream will automatically detect the number's base + representation. For example, if the number starts with "0x", it is + assumed to be in hexadecimal form. If it starts with the digits + 1-9, it is assumed to be in decimal form, and so on. You can set + the integer base, thereby disabling the automatic detection, by + calling setIntegerBase(). Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 2 + + QTextStream supports many formatting options for generating text. + You can set the field width and pad character by calling + setFieldWidth() and setPadChar(). Use setFieldAlignment() to set + the alignment within each field. For real numbers, call + setRealNumberNotation() and setRealNumberPrecision() to set the + notation (SmartNotation, ScientificNotation, FixedNotation) and precision in + digits of the generated number. Some extra number formatting + options are also available through setNumberFlags(). + + \keyword QTextStream manipulators + + Like \c <iostream> in the standard C++ library, QTextStream also + defines several global manipulator functions: + + \table + \header \o Manipulator \o Description + \row \o \c bin \o Same as setIntegerBase(2). + \row \o \c oct \o Same as setIntegerBase(8). + \row \o \c dec \o Same as setIntegerBase(10). + \row \o \c hex \o Same as setIntegerBase(16). + \row \o \c showbase \o Same as setNumberFlags(numberFlags() | ShowBase). + \row \o \c forcesign \o Same as setNumberFlags(numberFlags() | ForceSign). + \row \o \c forcepoint \o Same as setNumberFlags(numberFlags() | ForcePoint). + \row \o \c noshowbase \o Same as setNumberFlags(numberFlags() & ~ShowBase). + \row \o \c noforcesign \o Same as setNumberFlags(numberFlags() & ~ForceSign). + \row \o \c noforcepoint \o Same as setNumberFlags(numberFlags() & ~ForcePoint). + \row \o \c uppercasebase \o Same as setNumberFlags(numberFlags() | UppercaseBase). + \row \o \c uppercasedigits \o Same as setNumberFlags(numberFlags() | UppercaseDigits). + \row \o \c lowercasebase \o Same as setNumberFlags(numberFlags() & ~UppercaseBase). + \row \o \c lowercasedigits \o Same as setNumberFlags(numberFlags() & ~UppercaseDigits). + \row \o \c fixed \o Same as setRealNumberNotation(FixedNotation). + \row \o \c scientific \o Same as setRealNumberNotation(ScientificNotation). + \row \o \c left \o Same as setFieldAlignment(AlignLeft). + \row \o \c right \o Same as setFieldAlignment(AlignRight). + \row \o \c center \o Same as setFieldAlignment(AlignCenter). + \row \o \c endl \o Same as operator<<('\n') and flush(). + \row \o \c flush \o Same as flush(). + \row \o \c reset \o Same as reset(). + \row \o \c ws \o Same as skipWhiteSpace(). + \row \o \c bom \o Same as setGenerateByteOrderMark(true). + \endtable + + In addition, Qt provides three global manipulators that take a + parameter: qSetFieldWidth(), qSetPadChar(), and + qSetRealNumberPrecision(). + + \sa QDataStream, QIODevice, QFile, QBuffer, QTcpSocket, {Codecs Example} +*/ + +/*! \enum QTextStream::RealNumberNotation + + This enum specifies which notations to use for expressing \c + float and \c double as strings. + + \value ScientificNotation Scientific notation (\c{printf()}'s \c %e flag). + \value FixedNotation Fixed-point notation (\c{printf()}'s \c %f flag). + \value SmartNotation Scientific or fixed-point notation, depending on which makes most sense (\c{printf()}'s \c %g flag). + + \sa setRealNumberNotation() +*/ + +/*! \enum QTextStream::FieldAlignment + + This enum specifies how to align text in fields when the field is + wider than the text that occupies it. + + \value AlignLeft Pad on the right side of fields. + \value AlignRight Pad on the left side of fields. + \value AlignCenter Pad on both sides of field. + \value AlignAccountingStyle Same as AlignRight, except that the + sign of a number is flush left. + + \sa setFieldAlignment() +*/ + +/*! \enum QTextStream::NumberFlag + + This enum specifies various flags that can be set to affect the + output of integers, \c{float}s, and \c{double}s. + + \value ShowBase Show the base as a prefix if the base + is 16 ("0x"), 8 ("0"), or 2 ("0b"). + \value ForcePoint Always put the decimal separator in numbers, even if + there are no decimals. + \value ForceSign Always put the sign in numbers, even for positive numbers. + \value UppercaseBase Use uppercase versions of base prefixes ("0X", "0B"). + \value UppercaseDigits Use uppercase letters for expressing + digits 10 to 35 instead of lowercase. + + \sa setNumberFlags() +*/ + +/*! \enum QTextStream::Status + + This enum describes the current status of the text stream. + + \value Ok The text stream is operating normally. + \value ReadPastEnd The text stream has read past the end of the + data in the underlying device. + \value ReadCorruptData The text stream has read corrupt data. + \value WriteFailed The text stream cannot write to the underlying device. + + \sa status() +*/ + +#include "qtextstream.h" +#include "qbuffer.h" +#include "qfile.h" +#include "qnumeric.h" +#ifndef QT_NO_TEXTCODEC +#include "qtextcodec.h" +#endif +#ifndef Q_OS_WINCE +#include <locale.h> +#endif +#include "private/qlocale_p.h" + +#include <stdlib.h> +#include <limits.h> +#include <new> + +#if defined QTEXTSTREAM_DEBUG +#include <ctype.h> + +QT_BEGIN_NAMESPACE + +// Returns a human readable representation of the first \a len +// characters in \a data. +static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(int(uchar(c)))) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\x%x", (unsigned int)(unsigned char)c); + out += tmp.toLatin1(); + } + } + + if (len < maxSize) + out += "..."; + + return out; +} +QT_END_NAMESPACE + +#endif + +// A precondition macro +#define Q_VOID +#define CHECK_VALID_STREAM(x) do { \ + if (!d->string && !d->device) { \ + qWarning("QTextStream: No device"); \ + return x; \ + } } while (0) + +// Base implementations of operator>> for ints and reals +#define IMPLEMENT_STREAM_RIGHT_INT_OPERATOR(type) do { \ + Q_D(QTextStream); \ + CHECK_VALID_STREAM(*this); \ + qulonglong tmp; \ + switch (d->getNumber(&tmp)) { \ + case QTextStreamPrivate::npsOk: \ + i = (type)tmp; \ + break; \ + case QTextStreamPrivate::npsMissingDigit: \ + case QTextStreamPrivate::npsInvalidPrefix: \ + i = (type)0; \ + setStatus(atEnd() ? QTextStream::ReadPastEnd : QTextStream::ReadCorruptData); \ + break; \ + } \ + return *this; } while (0) + +#define IMPLEMENT_STREAM_RIGHT_REAL_OPERATOR(type) do { \ + Q_D(QTextStream); \ + CHECK_VALID_STREAM(*this); \ + double tmp; \ + if (d->getReal(&tmp)) { \ + f = (type)tmp; \ + } else { \ + f = (type)0; \ + setStatus(atEnd() ? QTextStream::ReadPastEnd : QTextStream::ReadCorruptData); \ + } \ + return *this; } while (0) + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QOBJECT +class QDeviceClosedNotifier : public QObject +{ + Q_OBJECT +public: + inline QDeviceClosedNotifier() + { } + + inline void setupDevice(QTextStream *stream, QIODevice *device) + { + disconnect(); + if (device) + connect(device, SIGNAL(aboutToClose()), this, SLOT(flushStream())); + this->stream = stream; + } + +public Q_SLOTS: + inline void flushStream() { stream->flush(); } + +private: + QTextStream *stream; +}; +#endif + +//------------------------------------------------------------------- +class QTextStreamPrivate +{ + Q_DECLARE_PUBLIC(QTextStream) +public: + QTextStreamPrivate(QTextStream *q_ptr); + ~QTextStreamPrivate(); + void reset(); + + // device + QIODevice *device; +#ifndef QT_NO_QOBJECT + QDeviceClosedNotifier deviceClosedNotifier; +#endif + bool deleteDevice; + + // string + QString *string; + int stringOffset; + QIODevice::OpenMode stringOpenMode; + +#ifndef QT_NO_TEXTCODEC + // codec + QTextCodec *codec; + QTextCodec::ConverterState readConverterState; + QTextCodec::ConverterState writeConverterState; + QTextCodec::ConverterState *readConverterSavedState; + bool autoDetectUnicode; +#endif + + // i/o + enum TokenDelimiter { + Space, + NotSpace, + EndOfLine + }; + + QString read(int maxlen); + bool scan(const QChar **ptr, int *tokenLength, + int maxlen, TokenDelimiter delimiter); + inline const QChar *readPtr() const; + inline void consumeLastToken(); + inline void consume(int nchars); + void saveConverterState(qint64 newPos); + void restoreToSavedConverterState(); + int lastTokenSize; + + // Return value type for getNumber() + enum NumberParsingStatus { + npsOk, + npsMissingDigit, + npsInvalidPrefix + }; + + inline bool getChar(QChar *ch); + inline void ungetChar(const QChar &ch); + NumberParsingStatus getNumber(qulonglong *l); + bool getReal(double *f); + + inline void write(const QString &data); + inline void putString(const QString &ch, bool number = false); + void putNumber(qulonglong number, bool negative); + + // buffers + bool fillReadBuffer(qint64 maxBytes = -1); + void resetReadBuffer(); + void flushWriteBuffer(); + QString writeBuffer; + QString readBuffer; + int readBufferOffset; + int readConverterSavedStateOffset; //the offset between readBufferStartDevicePos and that start of the buffer + qint64 readBufferStartDevicePos; + + // streaming parameters + int realNumberPrecision; + int integerBase; + int fieldWidth; + QChar padChar; + QTextStream::FieldAlignment fieldAlignment; + QTextStream::RealNumberNotation realNumberNotation; + QTextStream::NumberFlags numberFlags; + + // status + QTextStream::Status status; + + QLocale locale; + + QTextStream *q_ptr; +}; + +/*! \internal +*/ +QTextStreamPrivate::QTextStreamPrivate(QTextStream *q_ptr) + : +#ifndef QT_NO_TEXTCODEC + readConverterSavedState(0), +#endif + readConverterSavedStateOffset(0), + locale(QLocale::c()) +{ + this->q_ptr = q_ptr; + reset(); +} + +/*! \internal +*/ +QTextStreamPrivate::~QTextStreamPrivate() +{ + if (deleteDevice) { +#ifndef QT_NO_QOBJECT + device->blockSignals(true); +#endif + delete device; + } +#ifndef QT_NO_TEXTCODEC + delete readConverterSavedState; +#endif +} + +#ifndef QT_NO_TEXTCODEC +static void resetCodecConverterStateHelper(QTextCodec::ConverterState *state) +{ + state->~ConverterState(); + new (state) QTextCodec::ConverterState; +} + +static void copyConverterStateHelper(QTextCodec::ConverterState *dest, + const QTextCodec::ConverterState *src) +{ + // ### QTextCodec::ConverterState's copy constructors and assignments are + // private. This function copies the structure manually. + Q_ASSERT(!src->d); + dest->flags = src->flags; + dest->invalidChars = src->invalidChars; + dest->state_data[0] = src->state_data[0]; + dest->state_data[1] = src->state_data[1]; + dest->state_data[2] = src->state_data[2]; +} +#endif + +/*! \internal +*/ +void QTextStreamPrivate::reset() +{ + realNumberPrecision = 6; + integerBase = 0; + fieldWidth = 0; + padChar = QLatin1Char(' '); + fieldAlignment = QTextStream::AlignRight; + realNumberNotation = QTextStream::SmartNotation; + numberFlags = 0; + + device = 0; + deleteDevice = false; + string = 0; + stringOffset = 0; + stringOpenMode = QIODevice::NotOpen; + + readBufferOffset = 0; + readBufferStartDevicePos = 0; + lastTokenSize = 0; + +#ifndef QT_NO_TEXTCODEC + codec = QTextCodec::codecForLocale(); + resetCodecConverterStateHelper(&readConverterState); + resetCodecConverterStateHelper(&writeConverterState); + delete readConverterSavedState; + readConverterSavedState = 0; + writeConverterState.flags |= QTextCodec::IgnoreHeader; + autoDetectUnicode = true; +#endif +} + +/*! \internal +*/ +bool QTextStreamPrivate::fillReadBuffer(qint64 maxBytes) +{ + // no buffer next to the QString itself; this function should only + // be called internally, for devices. + Q_ASSERT(!string); + Q_ASSERT(device); + + // handle text translation and bypass the Text flag in the device. + bool textModeEnabled = device->isTextModeEnabled(); + if (textModeEnabled) + device->setTextModeEnabled(false); + + // read raw data into a temporary buffer + char buf[QTEXTSTREAM_BUFFERSIZE]; + qint64 bytesRead = 0; +#if defined(Q_OS_WIN) + // On Windows, there is no non-blocking stdin - so we fall back to reading + // lines instead. If there is no QOBJECT, we read lines for all sequential + // devices; otherwise, we read lines only for stdin. + QFile *file = 0; + Q_UNUSED(file); + if (device->isSequential() +#if !defined(QT_NO_QOBJECT) + && (file = qobject_cast<QFile *>(device)) && file->handle() == 0 +#endif + ) { + if (maxBytes != -1) + bytesRead = device->readLine(buf, qMin<qint64>(sizeof(buf), maxBytes)); + else + bytesRead = device->readLine(buf, sizeof(buf)); + } else +#endif + { + if (maxBytes != -1) + bytesRead = device->read(buf, qMin<qint64>(sizeof(buf), maxBytes)); + else + bytesRead = device->read(buf, sizeof(buf)); + } + +#ifndef QT_NO_TEXTCODEC + // codec auto detection, explicitly defaults to locale encoding if the + // codec has been set to 0. + if (!codec || autoDetectUnicode) { + autoDetectUnicode = false; + + codec = QTextCodec::codecForUtfText(QByteArray::fromRawData(buf, bytesRead), codec); + if (!codec) { + codec = QTextCodec::codecForLocale(); + writeConverterState.flags |= QTextCodec::IgnoreHeader; + } + } +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStreamPrivate::fillReadBuffer(), using %s codec", + codec->name().constData()); +#endif +#endif + +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStreamPrivate::fillReadBuffer(), device->read(\"%s\", %d) == %d", + qt_prettyDebug(buf, qMin(32,int(bytesRead)) , int(bytesRead)).constData(), sizeof(buf), int(bytesRead)); +#endif + + if (bytesRead <= 0) + return false; + + int oldReadBufferSize = readBuffer.size(); +#ifndef QT_NO_TEXTCODEC + // convert to unicode + readBuffer += codec->toUnicode(buf, bytesRead, &readConverterState); +#else + readBuffer += QString::fromLatin1(QByteArray(buf, bytesRead).constData()); +#endif + + // reset the Text flag. + if (textModeEnabled) + device->setTextModeEnabled(true); + + // remove all '\r\n' in the string. + if (readBuffer.size() > oldReadBufferSize && textModeEnabled) { + QChar CR = QLatin1Char('\r'); + QChar *writePtr = readBuffer.data() + oldReadBufferSize; + QChar *readPtr = readBuffer.data() + oldReadBufferSize; + QChar *endPtr = readBuffer.data() + readBuffer.size(); + + int n = oldReadBufferSize; + if (readPtr < endPtr) { + // Cut-off to avoid unnecessary self-copying. + while (*readPtr++ != CR) { + ++n; + if (++writePtr == endPtr) + break; + } + } + while (readPtr < endPtr) { + QChar ch = *readPtr++; + if (ch != CR) { + *writePtr++ = ch; + } else { + if (n < readBufferOffset) + --readBufferOffset; + --bytesRead; + } + ++n; + } + readBuffer.resize(writePtr - readBuffer.data()); + } + +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStreamPrivate::fillReadBuffer() read %d bytes from device. readBuffer = [%s]", int(bytesRead), + qt_prettyDebug(readBuffer.toLatin1(), readBuffer.size(), readBuffer.size()).data()); +#endif + return true; +} + +/*! \internal +*/ +void QTextStreamPrivate::resetReadBuffer() +{ + readBuffer.clear(); + readBufferOffset = 0; + readBufferStartDevicePos = (device ? device->pos() : 0); +} + +/*! \internal +*/ +void QTextStreamPrivate::flushWriteBuffer() +{ + // no buffer next to the QString itself; this function should only + // be called internally, for devices. + if (string || !device) + return; + + // Stream went bye-bye already. Appending further data may succeed again, + // but would create a corrupted stream anyway. + if (status != QTextStream::Ok) + return; + + if (writeBuffer.isEmpty()) + return; + +#if defined (Q_OS_WIN) + // handle text translation and bypass the Text flag in the device. + bool textModeEnabled = device->isTextModeEnabled(); + if (textModeEnabled) { + device->setTextModeEnabled(false); + writeBuffer.replace(QLatin1Char('\n'), QLatin1String("\r\n")); + } +#endif + +#ifndef QT_NO_TEXTCODEC + if (!codec) + codec = QTextCodec::codecForLocale(); +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStreamPrivate::flushWriteBuffer(), using %s codec (%s generating BOM)", + codec->name().constData(), writeConverterState.flags & QTextCodec::IgnoreHeader ? "not" : ""); +#endif + + // convert from unicode to raw data + QByteArray data = codec->fromUnicode(writeBuffer.data(), writeBuffer.size(), &writeConverterState); +#else + QByteArray data = writeBuffer.toLocal8Bit(); +#endif + writeBuffer.clear(); + + // write raw data to the device + qint64 bytesWritten = device->write(data); +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStreamPrivate::flushWriteBuffer(), device->write(\"%s\") == %d", + qt_prettyDebug(data.constData(), qMin(data.size(),32), data.size()).constData(), int(bytesWritten)); +#endif + if (bytesWritten <= 0) { + status = QTextStream::WriteFailed; + return; + } + +#if defined (Q_OS_WIN) + // replace the text flag + if (textModeEnabled) + device->setTextModeEnabled(true); +#endif + + // flush the file +#ifndef QT_NO_QOBJECT + QFile *file = qobject_cast<QFile *>(device); + bool flushed = !file || file->flush(); +#else + bool flushed = true; +#endif + +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStreamPrivate::flushWriteBuffer() wrote %d bytes", + int(bytesWritten)); +#endif + if (!flushed || bytesWritten != qint64(data.size())) + status = QTextStream::WriteFailed; +} + +QString QTextStreamPrivate::read(int maxlen) +{ + QString ret; + if (string) { + lastTokenSize = qMin(maxlen, string->size() - stringOffset); + ret = string->mid(stringOffset, lastTokenSize); + } else { + while (readBuffer.size() - readBufferOffset < maxlen && fillReadBuffer()) ; + lastTokenSize = qMin(maxlen, readBuffer.size() - readBufferOffset); + ret = readBuffer.mid(readBufferOffset, lastTokenSize); + } + consumeLastToken(); + +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStreamPrivate::read() maxlen = %d, token length = %d", maxlen, ret.length()); +#endif + return ret; +} + +/*! \internal + + Scans no more than \a maxlen QChars in the current buffer for the + first \a delimiter. Stores a pointer to the start offset of the + token in \a ptr, and the length in QChars in \a length. +*/ +bool QTextStreamPrivate::scan(const QChar **ptr, int *length, int maxlen, TokenDelimiter delimiter) +{ + int totalSize = 0; + int delimSize = 0; + bool consumeDelimiter = false; + bool foundToken = false; + int startOffset = device ? readBufferOffset : stringOffset; + QChar lastChar; + + bool canStillReadFromDevice = true; + do { + int endOffset; + const QChar *chPtr; + if (device) { + chPtr = readBuffer.constData(); + endOffset = readBuffer.size(); + } else { + chPtr = string->constData(); + endOffset = string->size(); + } + chPtr += startOffset; + + for (; !foundToken && startOffset < endOffset && (!maxlen || totalSize < maxlen); ++startOffset) { + const QChar ch = *chPtr++; + ++totalSize; + + switch (delimiter) { + case Space: + if (ch.isSpace()) { + foundToken = true; + delimSize = 1; + } + break; + case NotSpace: + if (!ch.isSpace()) { + foundToken = true; + delimSize = 1; + } + break; + case EndOfLine: + if (ch == QLatin1Char('\n')) { + foundToken = true; + delimSize = (lastChar == QLatin1Char('\r')) ? 2 : 1; + consumeDelimiter = true; + } + lastChar = ch; + break; + } + } + } while (!foundToken + && (!maxlen || totalSize < maxlen) + && (device && (canStillReadFromDevice = fillReadBuffer()))); + + // if the token was not found, but we reached the end of input, + // then we accept what we got. if we are not at the end of input, + // we return false. + if (!foundToken && (!maxlen || totalSize < maxlen) + && (totalSize == 0 + || (string && stringOffset + totalSize < string->size()) + || (device && !device->atEnd() && canStillReadFromDevice))) { +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStreamPrivate::scan() did not find the token."); +#endif + return false; + } + + // if we find a '\r' at the end of the data when reading lines, + // don't make it part of the line. + if (delimiter == EndOfLine && totalSize > 0 && !foundToken) { + if (((string && stringOffset + totalSize == string->size()) || (device && device->atEnd())) + && lastChar == QLatin1Char('\r')) { + consumeDelimiter = true; + ++delimSize; + } + } + + // set the read offset and length of the token + if (length) + *length = totalSize - delimSize; + if (ptr) + *ptr = readPtr(); + + // update last token size. the callee will call consumeLastToken() when + // done. + lastTokenSize = totalSize; + if (!consumeDelimiter) + lastTokenSize -= delimSize; + +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStreamPrivate::scan(%p, %p, %d, %x) token length = %d, delimiter = %d", + ptr, length, maxlen, (int)delimiter, totalSize - delimSize, delimSize); +#endif + return true; +} + +/*! \internal +*/ +inline const QChar *QTextStreamPrivate::readPtr() const +{ + Q_ASSERT(readBufferOffset <= readBuffer.size()); + if (string) + return string->constData() + stringOffset; + return readBuffer.constData() + readBufferOffset; +} + +/*! \internal +*/ +inline void QTextStreamPrivate::consumeLastToken() +{ + if (lastTokenSize) + consume(lastTokenSize); + lastTokenSize = 0; +} + +/*! \internal +*/ +inline void QTextStreamPrivate::consume(int size) +{ +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStreamPrivate::consume(%d)", size); +#endif + if (string) { + stringOffset += size; + if (stringOffset > string->size()) + stringOffset = string->size(); + } else { + readBufferOffset += size; + if (readBufferOffset >= readBuffer.size()) { + readBufferOffset = 0; + readBuffer.clear(); + saveConverterState(device->pos()); + } else if (readBufferOffset > QTEXTSTREAM_BUFFERSIZE) { + readBuffer = readBuffer.remove(0,readBufferOffset); + readConverterSavedStateOffset += readBufferOffset; + readBufferOffset = 0; + } + } +} + +/*! \internal +*/ +inline void QTextStreamPrivate::saveConverterState(qint64 newPos) +{ +#ifndef QT_NO_TEXTCODEC + if (readConverterState.d) { + // converter cannot be copied, so don't save anything + // don't update readBufferStartDevicePos either + return; + } + + if (!readConverterSavedState) + readConverterSavedState = new QTextCodec::ConverterState; + copyConverterStateHelper(readConverterSavedState, &readConverterState); +#endif + + readBufferStartDevicePos = newPos; + readConverterSavedStateOffset = 0; +} + +/*! \internal +*/ +inline void QTextStreamPrivate::restoreToSavedConverterState() +{ +#ifndef QT_NO_TEXTCODEC + if (readConverterSavedState) { + // we have a saved state + // that means the converter can be copied + copyConverterStateHelper(&readConverterState, readConverterSavedState); + } else { + // the only state we could save was the initial + // so reset to that + resetCodecConverterStateHelper(&readConverterState); + } +#endif +} + +/*! \internal +*/ +inline void QTextStreamPrivate::write(const QString &data) +{ + if (string) { + // ### What about seek()?? + string->append(data); + } else { + writeBuffer += data; + if (writeBuffer.size() > QTEXTSTREAM_BUFFERSIZE) + flushWriteBuffer(); + } +} + +/*! \internal +*/ +inline bool QTextStreamPrivate::getChar(QChar *ch) +{ + if ((string && stringOffset == string->size()) + || (device && readBuffer.isEmpty() && !fillReadBuffer())) { + if (ch) + *ch = 0; + return false; + } + if (ch) + *ch = *readPtr(); + consume(1); + return true; +} + +/*! \internal +*/ +inline void QTextStreamPrivate::ungetChar(const QChar &ch) +{ + if (string) { + if (stringOffset == 0) + string->prepend(ch); + else + (*string)[--stringOffset] = ch; + return; + } + + if (readBufferOffset == 0) { + readBuffer.prepend(ch); + return; + } + + readBuffer[--readBufferOffset] = ch; +} + +/*! \internal +*/ +inline void QTextStreamPrivate::putString(const QString &s, bool number) +{ + QString tmp = s; + + // handle padding + int padSize = fieldWidth - s.size(); + if (padSize > 0) { + QString pad(padSize, padChar); + if (fieldAlignment == QTextStream::AlignLeft) { + tmp.append(QString(padSize, padChar)); + } else if (fieldAlignment == QTextStream::AlignRight + || fieldAlignment == QTextStream::AlignAccountingStyle) { + tmp.prepend(QString(padSize, padChar)); + if (fieldAlignment == QTextStream::AlignAccountingStyle && number) { + const QChar sign = s.size() > 0 ? s.at(0) : QChar(); + if (sign == locale.negativeSign() || sign == locale.positiveSign()) { + QChar *data = tmp.data(); + data[padSize] = tmp.at(0); + data[0] = sign; + } + } + } else if (fieldAlignment == QTextStream::AlignCenter) { + tmp.prepend(QString(padSize/2, padChar)); + tmp.append(QString(padSize - padSize/2, padChar)); + } + } + +#if defined (QTEXTSTREAM_DEBUG) + QByteArray a = s.toUtf8(); + QByteArray b = tmp.toUtf8(); + qDebug("QTextStreamPrivate::putString(\"%s\") calls write(\"%s\")", + qt_prettyDebug(a.constData(), a.size(), qMax(16, a.size())).constData(), + qt_prettyDebug(b.constData(), b.size(), qMax(16, b.size())).constData()); +#endif + write(tmp); +} + +/*! + Constructs a QTextStream. Before you can use it for reading or + writing, you must assign a device or a string. + + \sa setDevice(), setString() +*/ +QTextStream::QTextStream() + : d_ptr(new QTextStreamPrivate(this)) +{ +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStream::QTextStream()"); +#endif + Q_D(QTextStream); + d->status = Ok; +} + +/*! + Constructs a QTextStream that operates on \a device. +*/ +QTextStream::QTextStream(QIODevice *device) + : d_ptr(new QTextStreamPrivate(this)) +{ +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStream::QTextStream(QIODevice *device == *%p)", + device); +#endif + Q_D(QTextStream); + d->device = device; +#ifndef QT_NO_QOBJECT + d->deviceClosedNotifier.setupDevice(this, d->device); +#endif + d->status = Ok; +} + +/*! + Constructs a QTextStream that operates on \a string, using \a + openMode to define the open mode. +*/ +QTextStream::QTextStream(QString *string, QIODevice::OpenMode openMode) + : d_ptr(new QTextStreamPrivate(this)) +{ +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStream::QTextStream(QString *string == *%p, openMode = %d)", + string, int(openMode)); +#endif + Q_D(QTextStream); + d->string = string; + d->stringOpenMode = openMode; + d->status = Ok; +} + +/*! + Constructs a QTextStream that operates on \a array, using \a + openMode to define the open mode. Internally, the array is wrapped + by a QBuffer. +*/ +QTextStream::QTextStream(QByteArray *array, QIODevice::OpenMode openMode) + : d_ptr(new QTextStreamPrivate(this)) +{ +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStream::QTextStream(QByteArray *array == *%p, openMode = %d)", + array, int(openMode)); +#endif + Q_D(QTextStream); + d->device = new QBuffer(array); + d->device->open(openMode); + d->deleteDevice = true; +#ifndef QT_NO_QOBJECT + d->deviceClosedNotifier.setupDevice(this, d->device); +#endif + d->status = Ok; +} + +/*! + Constructs a QTextStream that operates on \a array, using \a + openMode to define the open mode. The array is accessed as + read-only, regardless of the values in \a openMode. + + This constructor is convenient for working on constant + strings. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 3 +*/ +QTextStream::QTextStream(const QByteArray &array, QIODevice::OpenMode openMode) + : d_ptr(new QTextStreamPrivate(this)) +{ +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStream::QTextStream(const QByteArray &array == *(%p), openMode = %d)", + &array, int(openMode)); +#endif + QBuffer *buffer = new QBuffer; + buffer->setData(array); + buffer->open(openMode); + + Q_D(QTextStream); + d->device = buffer; + d->deleteDevice = true; +#ifndef QT_NO_QOBJECT + d->deviceClosedNotifier.setupDevice(this, d->device); +#endif + d->status = Ok; +} + +/*! + Constructs a QTextStream that operates on \a fileHandle, using \a + openMode to define the open mode. Internally, a QFile is created + to handle the FILE pointer. + + This constructor is useful for working directly with the common + FILE based input and output streams: stdin, stdout and stderr. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 4 +*/ + +QTextStream::QTextStream(FILE *fileHandle, QIODevice::OpenMode openMode) + : d_ptr(new QTextStreamPrivate(this)) +{ +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStream::QTextStream(FILE *fileHandle = %p, openMode = %d)", + fileHandle, int(openMode)); +#endif + QFile *file = new QFile; + file->open(fileHandle, openMode); + + Q_D(QTextStream); + d->device = file; + d->deleteDevice = true; +#ifndef QT_NO_QOBJECT + d->deviceClosedNotifier.setupDevice(this, d->device); +#endif + d->status = Ok; +} + +/*! + Destroys the QTextStream. + + If the stream operates on a device, flush() will be called + implicitly. Otherwise, the device is unaffected. +*/ +QTextStream::~QTextStream() +{ + Q_D(QTextStream); +#if defined (QTEXTSTREAM_DEBUG) + qDebug("QTextStream::~QTextStream()"); +#endif + if (!d->writeBuffer.isEmpty()) + d->flushWriteBuffer(); +} + +/*! + Resets QTextStream's formatting options, bringing it back to its + original constructed state. The device, string and any buffered + data is left untouched. +*/ +void QTextStream::reset() +{ + Q_D(QTextStream); + + d->realNumberPrecision = 6; + d->integerBase = 0; + d->fieldWidth = 0; + d->padChar = QLatin1Char(' '); + d->fieldAlignment = QTextStream::AlignRight; + d->realNumberNotation = QTextStream::SmartNotation; + d->numberFlags = 0; +} + +/*! + Flushes any buffered data waiting to be written to the device. + + If QTextStream operates on a string, this function does nothing. +*/ +void QTextStream::flush() +{ + Q_D(QTextStream); + d->flushWriteBuffer(); +} + +/*! + Seeks to the position \a pos in the device. Returns true on + success; otherwise returns false. +*/ +bool QTextStream::seek(qint64 pos) +{ + Q_D(QTextStream); + d->lastTokenSize = 0; + + if (d->device) { + // Empty the write buffer + d->flushWriteBuffer(); + if (!d->device->seek(pos)) + return false; + d->resetReadBuffer(); + +#ifndef QT_NO_TEXTCODEC + // Reset the codec converter states. + resetCodecConverterStateHelper(&d->readConverterState); + resetCodecConverterStateHelper(&d->writeConverterState); + delete d->readConverterSavedState; + d->readConverterSavedState = 0; + d->writeConverterState.flags |= QTextCodec::IgnoreHeader; +#endif + return true; + } + + // string + if (d->string && pos <= d->string->size()) { + d->stringOffset = int(pos); + return true; + } + return false; +} + +/*! + \since 4.2 + + Returns the device position corresponding to the current position of the + stream, or -1 if an error occurs (e.g., if there is no device or string, + or if there's a device error). + + Because QTextStream is buffered, this function may have to + seek the device to reconstruct a valid device position. This + operation can be expensive, so you may want to avoid calling this + function in a tight loop. + + \sa seek() +*/ +qint64 QTextStream::pos() const +{ + Q_D(const QTextStream); + if (d->device) { + // Cutoff + if (d->readBuffer.isEmpty()) + return d->device->pos(); + if (d->device->isSequential()) + return 0; + + // Seek the device + if (!d->device->seek(d->readBufferStartDevicePos)) + return qint64(-1); + + // Reset the read buffer + QTextStreamPrivate *thatd = const_cast<QTextStreamPrivate *>(d); + thatd->readBuffer.clear(); + +#ifndef QT_NO_TEXTCODEC + thatd->restoreToSavedConverterState(); + if (d->readBufferStartDevicePos == 0) + thatd->autoDetectUnicode = true; +#endif + + // Rewind the device to get to the current position Ensure that + // readBufferOffset is unaffected by fillReadBuffer() + int oldReadBufferOffset = d->readBufferOffset + d->readConverterSavedStateOffset; + while (d->readBuffer.size() < oldReadBufferOffset) { + if (!thatd->fillReadBuffer(1)) + return qint64(-1); + } + thatd->readBufferOffset = oldReadBufferOffset; + thatd->readConverterSavedStateOffset = 0; + + // Return the device position. + return d->device->pos(); + } + + if (d->string) + return d->stringOffset; + + qWarning("QTextStream::pos: no device"); + return qint64(-1); +} + +/*! + Reads and discards whitespace from the stream until either a + non-space character is detected, or until atEnd() returns + true. This function is useful when reading a stream character by + character. + + Whitespace characters are all characters for which + QChar::isSpace() returns true. + + \sa operator>>() +*/ +void QTextStream::skipWhiteSpace() +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(Q_VOID); + d->scan(0, 0, 0, QTextStreamPrivate::NotSpace); + d->consumeLastToken(); +} + +/*! + Sets the current device to \a device. If a device has already been + assigned, QTextStream will call flush() before the old device is + replaced. + + \note This function resets locale to the default locale ('C') + and codec to the default codec, QTextCodec::codecForLocale(). + + \sa device(), setString() +*/ +void QTextStream::setDevice(QIODevice *device) +{ + Q_D(QTextStream); + flush(); + if (d->deleteDevice) { +#ifndef QT_NO_QOBJECT + d->deviceClosedNotifier.disconnect(); +#endif + delete d->device; + d->deleteDevice = false; + } + + d->reset(); + d->status = Ok; + d->device = device; + d->resetReadBuffer(); +#ifndef QT_NO_QOBJECT + d->deviceClosedNotifier.setupDevice(this, d->device); +#endif +} + +/*! + Returns the current device associated with the QTextStream, + or 0 if no device has been assigned. + + \sa setDevice(), string() +*/ +QIODevice *QTextStream::device() const +{ + Q_D(const QTextStream); + return d->device; +} + +/*! + Sets the current string to \a string, using the given \a + openMode. If a device has already been assigned, QTextStream will + call flush() before replacing it. + + \sa string(), setDevice() +*/ +void QTextStream::setString(QString *string, QIODevice::OpenMode openMode) +{ + Q_D(QTextStream); + flush(); + if (d->deleteDevice) { +#ifndef QT_NO_QOBJECT + d->deviceClosedNotifier.disconnect(); + d->device->blockSignals(true); +#endif + delete d->device; + d->deleteDevice = false; + } + + d->reset(); + d->status = Ok; + d->string = string; + d->stringOpenMode = openMode; +} + +/*! + Returns the current string assigned to the QTextStream, or 0 if no + string has been assigned. + + \sa setString(), device() +*/ +QString *QTextStream::string() const +{ + Q_D(const QTextStream); + return d->string; +} + +/*! + Sets the field alignment to \a mode. When used together with + setFieldWidth(), this function allows you to generate formatted + output with text aligned to the left, to the right or center + aligned. + + \sa fieldAlignment(), setFieldWidth() +*/ +void QTextStream::setFieldAlignment(FieldAlignment mode) +{ + Q_D(QTextStream); + d->fieldAlignment = mode; +} + +/*! + Returns the current field alignment. + + \sa setFieldAlignment(), fieldWidth() +*/ +QTextStream::FieldAlignment QTextStream::fieldAlignment() const +{ + Q_D(const QTextStream); + return d->fieldAlignment; +} + +/*! + Sets the pad character to \a ch. The default value is the ASCII + space character (' '), or QChar(0x20). This character is used to + fill in the space in fields when generating text. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 5 + + The string \c s contains: + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 6 + + \sa padChar(), setFieldWidth() +*/ +void QTextStream::setPadChar(QChar ch) +{ + Q_D(QTextStream); + d->padChar = ch; +} + +/*! + Returns the current pad character. + + \sa setPadChar(), setFieldWidth() +*/ +QChar QTextStream::padChar() const +{ + Q_D(const QTextStream); + return d->padChar; +} + +/*! + Sets the current field width to \a width. If \a width is 0 (the + default), the field width is equal to the length of the generated + text. + + \note The field width applies to every element appended to this + stream after this function has been called (e.g., it also pads + endl). This behavior is different from similar classes in the STL, + where the field width only applies to the next element. + + \sa fieldWidth(), setPadChar() +*/ +void QTextStream::setFieldWidth(int width) +{ + Q_D(QTextStream); + d->fieldWidth = width; +} + +/*! + Returns the current field width. + + \sa setFieldWidth() +*/ +int QTextStream::fieldWidth() const +{ + Q_D(const QTextStream); + return d->fieldWidth; +} + +/*! + Sets the current number flags to \a flags. \a flags is a set of + flags from the NumberFlag enum, and describes options for + formatting generated code (e.g., whether or not to always write + the base or sign of a number). + + \sa numberFlags(), setIntegerBase(), setRealNumberNotation() +*/ +void QTextStream::setNumberFlags(NumberFlags flags) +{ + Q_D(QTextStream); + d->numberFlags = flags; +} + +/*! + Returns the current number flags. + + \sa setNumberFlags(), integerBase(), realNumberNotation() +*/ +QTextStream::NumberFlags QTextStream::numberFlags() const +{ + Q_D(const QTextStream); + return d->numberFlags; +} + +/*! + Sets the base of integers to \a base, both for reading and for + generating numbers. \a base can be either 2 (binary), 8 (octal), + 10 (decimal) or 16 (hexadecimal). If \a base is 0, QTextStream + will attempt to detect the base by inspecting the data on the + stream. When generating numbers, QTextStream assumes base is 10 + unless the base has been set explicitly. + + \sa integerBase(), QString::number(), setNumberFlags() +*/ +void QTextStream::setIntegerBase(int base) +{ + Q_D(QTextStream); + d->integerBase = base; +} + +/*! + Returns the current base of integers. 0 means that the base is + detected when reading, or 10 (decimal) when generating numbers. + + \sa setIntegerBase(), QString::number(), numberFlags() +*/ +int QTextStream::integerBase() const +{ + Q_D(const QTextStream); + return d->integerBase; +} + +/*! + Sets the real number notation to \a notation (SmartNotation, + FixedNotation, ScientificNotation). When reading and generating + numbers, QTextStream uses this value to detect the formatting of + real numbers. + + \sa realNumberNotation(), setRealNumberPrecision(), setNumberFlags(), setIntegerBase() +*/ +void QTextStream::setRealNumberNotation(RealNumberNotation notation) +{ + Q_D(QTextStream); + d->realNumberNotation = notation; +} + +/*! + Returns the current real number notation. + + \sa setRealNumberNotation(), realNumberPrecision(), numberFlags(), integerBase() +*/ +QTextStream::RealNumberNotation QTextStream::realNumberNotation() const +{ + Q_D(const QTextStream); + return d->realNumberNotation; +} + +/*! + Sets the precision of real numbers to \a precision. This value + describes the number of fraction digits QTextStream should + write when generating real numbers. + + The precision cannot be a negative value. The default value is 6. + + \sa realNumberPrecision(), setRealNumberNotation() +*/ +void QTextStream::setRealNumberPrecision(int precision) +{ + Q_D(QTextStream); + if (precision < 0) { + qWarning("QTextStream::setRealNumberPrecision: Invalid precision (%d)", precision); + d->realNumberPrecision = 6; + return; + } + d->realNumberPrecision = precision; +} + +/*! + Returns the current real number precision, or the number of fraction + digits QTextStream will write when generating real numbers. + + \sa setRealNumberNotation(), realNumberNotation(), numberFlags(), integerBase() +*/ +int QTextStream::realNumberPrecision() const +{ + Q_D(const QTextStream); + return d->realNumberPrecision; +} + +/*! + Returns the status of the text stream. + + \sa QTextStream::Status, setStatus(), resetStatus() +*/ + +QTextStream::Status QTextStream::status() const +{ + Q_D(const QTextStream); + return d->status; +} + +/*! + \since 4.1 + + Resets the status of the text stream. + + \sa QTextStream::Status, status(), setStatus() +*/ +void QTextStream::resetStatus() +{ + Q_D(QTextStream); + d->status = Ok; +} + +/*! + \since 4.1 + + Sets the status of the text stream to the \a status given. + + Subsequent calls to setStatus() are ignored until resetStatus() + is called. + + \sa Status status() resetStatus() +*/ +void QTextStream::setStatus(Status status) +{ + Q_D(QTextStream); + if (d->status == Ok) + d->status = status; +} + +/*! + Returns true if there is no more data to be read from the + QTextStream; otherwise returns false. This is similar to, but not + the same as calling QIODevice::atEnd(), as QTextStream also takes + into account its internal Unicode buffer. +*/ +bool QTextStream::atEnd() const +{ + Q_D(const QTextStream); + CHECK_VALID_STREAM(true); + + if (d->string) + return d->string->size() == d->stringOffset; + return d->readBuffer.isEmpty() && d->device->atEnd(); +} + +/*! + Reads the entire content of the stream, and returns it as a + QString. Avoid this function when working on large files, as it + will consume a significant amount of memory. + + Calling readLine() is better if you do not know how much data is + available. + + \sa readLine() +*/ +QString QTextStream::readAll() +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(QString()); + + return d->read(INT_MAX); +} + +/*! + Reads one line of text from the stream, and returns it as a + QString. The maximum allowed line length is set to \a maxlen. If + the stream contains lines longer than this, then the lines will be + split after \a maxlen characters and returned in parts. + + If \a maxlen is 0, the lines can be of any length. A common value + for \a maxlen is 75. + + The returned line has no trailing end-of-line characters ("\\n" + or "\\r\\n"), so calling QString::trimmed() is unnecessary. + + If the stream has read to the end of the file, readLine() will return a + null QString. For strings, or for devices that support it, you can + explicitly test for the end of the stream using atEnd(). + + \sa readAll(), QIODevice::readLine() +*/ +QString QTextStream::readLine(qint64 maxlen) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(QString()); + + const QChar *readPtr; + int length; + if (!d->scan(&readPtr, &length, int(maxlen), QTextStreamPrivate::EndOfLine)) + return QString(); + + QString tmp = QString(readPtr, length); + d->consumeLastToken(); + return tmp; +} + +/*! + \since 4.1 + + Reads at most \a maxlen characters from the stream, and returns the data + read as a QString. + + \sa readAll(), readLine(), QIODevice::read() +*/ +QString QTextStream::read(qint64 maxlen) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(QString()); + + if (maxlen <= 0) + return QString::fromLatin1(""); // empty, not null + + return d->read(int(maxlen)); +} + +/*! \internal +*/ +QTextStreamPrivate::NumberParsingStatus QTextStreamPrivate::getNumber(qulonglong *ret) +{ + scan(0, 0, 0, NotSpace); + consumeLastToken(); + + // detect int encoding + int base = integerBase; + if (base == 0) { + QChar ch; + if (!getChar(&ch)) + return npsInvalidPrefix; + if (ch == QLatin1Char('0')) { + QChar ch2; + if (!getChar(&ch2)) { + // Result is the number 0 + *ret = 0; + return npsOk; + } + ch2 = ch2.toLower(); + + if (ch2 == QLatin1Char('x')) { + base = 16; + } else if (ch2 == QLatin1Char('b')) { + base = 2; + } else if (ch2.isDigit() && ch2.digitValue() >= 0 && ch2.digitValue() <= 7) { + base = 8; + } else { + base = 10; + } + ungetChar(ch2); + } else if (ch == locale.negativeSign() || ch == locale.positiveSign() || ch.isDigit()) { + base = 10; + } else { + ungetChar(ch); + return npsInvalidPrefix; + } + ungetChar(ch); + // State of the stream is now the same as on entry + // (cursor is at prefix), + // and local variable 'base' has been set appropriately. + } + + qulonglong val=0; + switch (base) { + case 2: { + QChar pf1, pf2, dig; + // Parse prefix '0b' + if (!getChar(&pf1) || pf1 != QLatin1Char('0')) + return npsInvalidPrefix; + if (!getChar(&pf2) || pf2.toLower() != QLatin1Char('b')) + return npsInvalidPrefix; + // Parse digits + int ndigits = 0; + while (getChar(&dig)) { + int n = dig.toLower().unicode(); + if (n == '0' || n == '1') { + val <<= 1; + val += n - '0'; + } else { + ungetChar(dig); + break; + } + ndigits++; + } + if (ndigits == 0) { + // Unwind the prefix and abort + ungetChar(pf2); + ungetChar(pf1); + return npsMissingDigit; + } + break; + } + case 8: { + QChar pf, dig; + // Parse prefix '0' + if (!getChar(&pf) || pf != QLatin1Char('0')) + return npsInvalidPrefix; + // Parse digits + int ndigits = 0; + while (getChar(&dig)) { + int n = dig.toLower().unicode(); + if (n >= '0' && n <= '7') { + val *= 8; + val += n - '0'; + } else { + ungetChar(dig); + break; + } + ndigits++; + } + if (ndigits == 0) { + // Unwind the prefix and abort + ungetChar(pf); + return npsMissingDigit; + } + break; + } + case 10: { + // Parse sign (or first digit) + QChar sign; + int ndigits = 0; + if (!getChar(&sign)) + return npsMissingDigit; + if (sign != locale.negativeSign() && sign != locale.positiveSign()) { + if (!sign.isDigit()) { + ungetChar(sign); + return npsMissingDigit; + } + val += sign.digitValue(); + ndigits++; + } + // Parse digits + QChar ch; + while (getChar(&ch)) { + if (ch.isDigit()) { + val *= 10; + val += ch.digitValue(); + } else if (locale != QLocale::c() && ch == locale.groupSeparator()) { + continue; + } else { + ungetChar(ch); + break; + } + ndigits++; + } + if (ndigits == 0) + return npsMissingDigit; + if (sign == locale.negativeSign()) { + qlonglong ival = qlonglong(val); + if (ival > 0) + ival = -ival; + val = qulonglong(ival); + } + break; + } + case 16: { + QChar pf1, pf2, dig; + // Parse prefix ' 0x' + if (!getChar(&pf1) || pf1 != QLatin1Char('0')) + return npsInvalidPrefix; + if (!getChar(&pf2) || pf2.toLower() != QLatin1Char('x')) + return npsInvalidPrefix; + // Parse digits + int ndigits = 0; + while (getChar(&dig)) { + int n = dig.toLower().unicode(); + if (n >= '0' && n <= '9') { + val <<= 4; + val += n - '0'; + } else if (n >= 'a' && n <= 'f') { + val <<= 4; + val += 10 + (n - 'a'); + } else { + ungetChar(dig); + break; + } + ndigits++; + } + if (ndigits == 0) { + return npsMissingDigit; + } + break; + } + default: + // Unsupported integerBase + return npsInvalidPrefix; + } + + if (ret) + *ret = val; + return npsOk; +} + +/*! \internal + (hihi) +*/ +bool QTextStreamPrivate::getReal(double *f) +{ + // We use a table-driven FSM to parse floating point numbers + // strtod() cannot be used directly since we may be reading from a + // QIODevice. + enum ParserState { + Init = 0, + Sign = 1, + Mantissa = 2, + Dot = 3, + Abscissa = 4, + ExpMark = 5, + ExpSign = 6, + Exponent = 7, + Nan1 = 8, + Nan2 = 9, + Inf1 = 10, + Inf2 = 11, + NanInf = 12, + Done = 13 + }; + enum InputToken { + None = 0, + InputSign = 1, + InputDigit = 2, + InputDot = 3, + InputExp = 4, + InputI = 5, + InputN = 6, + InputF = 7, + InputA = 8, + InputT = 9 + }; + + static const uchar table[13][10] = { + // None InputSign InputDigit InputDot InputExp InputI InputN InputF InputA InputT + { 0, Sign, Mantissa, Dot, 0, Inf1, Nan1, 0, 0, 0 }, // 0 Init + { 0, 0, Mantissa, Dot, 0, Inf1, Nan1, 0, 0, 0 }, // 1 Sign + { Done, Done, Mantissa, Dot, ExpMark, 0, 0, 0, 0, 0 }, // 2 Mantissa + { 0, 0, Abscissa, 0, 0, 0, 0, 0, 0, 0 }, // 3 Dot + { Done, Done, Abscissa, Done, ExpMark, 0, 0, 0, 0, 0 }, // 4 Abscissa + { 0, ExpSign, Exponent, 0, 0, 0, 0, 0, 0, 0 }, // 5 ExpMark + { 0, 0, Exponent, 0, 0, 0, 0, 0, 0, 0 }, // 6 ExpSign + { Done, Done, Exponent, Done, Done, 0, 0, 0, 0, 0 }, // 7 Exponent + { 0, 0, 0, 0, 0, 0, 0, 0, Nan2, 0 }, // 8 Nan1 + { 0, 0, 0, 0, 0, 0, NanInf, 0, 0, 0 }, // 9 Nan2 + { 0, 0, 0, 0, 0, 0, Inf2, 0, 0, 0 }, // 10 Inf1 + { 0, 0, 0, 0, 0, 0, 0, NanInf, 0, 0 }, // 11 Inf2 + { Done, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 11 NanInf + }; + + ParserState state = Init; + InputToken input = None; + + scan(0, 0, 0, NotSpace); + consumeLastToken(); + + const int BufferSize = 128; + char buf[BufferSize]; + int i = 0; + + QChar c; + while (getChar(&c)) { + switch (c.unicode()) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + input = InputDigit; + break; + case 'i': case 'I': + input = InputI; + break; + case 'n': case 'N': + input = InputN; + break; + case 'f': case 'F': + input = InputF; + break; + case 'a': case 'A': + input = InputA; + break; + case 't': case 'T': + input = InputT; + break; + default: { + QChar lc = c.toLower(); + if (lc == locale.decimalPoint().toLower()) + input = InputDot; + else if (lc == locale.exponential().toLower()) + input = InputExp; + else if (lc == locale.negativeSign().toLower() + || lc == locale.positiveSign().toLower()) + input = InputSign; + else if (locale != QLocale::c() // backward-compatibility + && lc == locale.groupSeparator().toLower()) + input = InputDigit; // well, it isn't a digit, but no one cares. + else + input = None; + } + break; + } + + state = ParserState(table[state][input]); + + if (state == Init || state == Done || i > (BufferSize - 5)) { + ungetChar(c); + if (i > (BufferSize - 5)) { // ignore rest of digits + while (getChar(&c)) { + if (!c.isDigit()) { + ungetChar(c); + break; + } + } + } + break; + } + + buf[i++] = c.toLatin1(); + } + + if (i == 0) + return false; + if (!f) + return true; + buf[i] = '\0'; + + // backward-compatibility. Old implementation supported +nan/-nan + // for some reason. QLocale only checks for lower-case + // nan/+inf/-inf, so here we also check for uppercase and mixed + // case versions. + if (!qstricmp(buf, "nan") || !qstricmp(buf, "+nan") || !qstricmp(buf, "-nan")) { + *f = qSNaN(); + return true; + } else if (!qstricmp(buf, "+inf") || !qstricmp(buf, "inf")) { + *f = qInf(); + return true; + } else if (!qstricmp(buf, "-inf")) { + *f = -qInf(); + return true; + } + bool ok; + *f = locale.toDouble(QString::fromLatin1(buf), &ok); + return ok; +} + +/*! + Reads a character from the stream and stores it in \a c. Returns a + reference to the QTextStream, so several operators can be + nested. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 7 + + Whitespace is \e not skipped. +*/ + +QTextStream &QTextStream::operator>>(QChar &c) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->scan(0, 0, 0, QTextStreamPrivate::NotSpace); + if (!d->getChar(&c)) + setStatus(ReadPastEnd); + return *this; +} + +/*! + \overload + + Reads a character from the stream and stores it in \a c. The + character from the stream is converted to ISO-5589-1 before it is + stored. + + \sa QChar::toLatin1() +*/ +QTextStream &QTextStream::operator>>(char &c) +{ + QChar ch; + *this >> ch; + c = ch.toLatin1(); + return *this; +} + +/*! + Reads an integer from the stream and stores it in \a i, then + returns a reference to the QTextStream. The number is cast to + the correct type before it is stored. If no number was detected on + the stream, \a i is set to 0. + + By default, QTextStream will attempt to detect the base of the + number using the following rules: + + \table + \header \o Prefix \o Base + \row \o "0b" or "0B" \o 2 (binary) + \row \o "0" followed by "0-7" \o 8 (octal) + \row \o "0" otherwise \o 10 (decimal) + \row \o "0x" or "0X" \o 16 (hexadecimal) + \row \o "1" to "9" \o 10 (decimal) + \endtable + + By calling setIntegerBase(), you can specify the integer base + explicitly. This will disable the auto-detection, and speed up + QTextStream slightly. + + Leading whitespace is skipped. +*/ +QTextStream &QTextStream::operator>>(signed short &i) +{ + IMPLEMENT_STREAM_RIGHT_INT_OPERATOR(signed short); +} + +/*! + \overload + + Stores the integer in the unsigned short \a i. +*/ +QTextStream &QTextStream::operator>>(unsigned short &i) +{ + IMPLEMENT_STREAM_RIGHT_INT_OPERATOR(unsigned short); +} + +/*! + \overload + + Stores the integer in the signed int \a i. +*/ +QTextStream &QTextStream::operator>>(signed int &i) +{ + IMPLEMENT_STREAM_RIGHT_INT_OPERATOR(signed int); +} + +/*! + \overload + + Stores the integer in the unsigned int \a i. +*/ +QTextStream &QTextStream::operator>>(unsigned int &i) +{ + IMPLEMENT_STREAM_RIGHT_INT_OPERATOR(unsigned int); +} + +/*! + \overload + + Stores the integer in the signed long \a i. +*/ +QTextStream &QTextStream::operator>>(signed long &i) +{ + IMPLEMENT_STREAM_RIGHT_INT_OPERATOR(signed long); +} + +/*! + \overload + + Stores the integer in the unsigned long \a i. +*/ +QTextStream &QTextStream::operator>>(unsigned long &i) +{ + IMPLEMENT_STREAM_RIGHT_INT_OPERATOR(unsigned long); +} + +/*! + \overload + + Stores the integer in the qlonglong \a i. +*/ +QTextStream &QTextStream::operator>>(qlonglong &i) +{ + IMPLEMENT_STREAM_RIGHT_INT_OPERATOR(qlonglong); +} + +/*! + \overload + + Stores the integer in the qulonglong \a i. +*/ +QTextStream &QTextStream::operator>>(qulonglong &i) +{ + IMPLEMENT_STREAM_RIGHT_INT_OPERATOR(qulonglong); +} + +/*! + Reads a real number from the stream and stores it in \a f, then + returns a reference to the QTextStream. The number is cast to + the correct type. If no real number is detect on the stream, \a f + is set to 0.0. + + As a special exception, QTextStream allows the strings "nan" and "inf" to + represent NAN and INF floats or doubles. + + Leading whitespace is skipped. +*/ +QTextStream &QTextStream::operator>>(float &f) +{ + IMPLEMENT_STREAM_RIGHT_REAL_OPERATOR(float); +} + +/*! + \overload + + Stores the real number in the double \a f. +*/ +QTextStream &QTextStream::operator>>(double &f) +{ + IMPLEMENT_STREAM_RIGHT_REAL_OPERATOR(double); +} + +/*! + Reads a word from the stream and stores it in \a str, then returns + a reference to the stream. Words are separated by whitespace + (i.e., all characters for which QChar::isSpace() returns true). + + Leading whitespace is skipped. +*/ +QTextStream &QTextStream::operator>>(QString &str) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + + str.clear(); + d->scan(0, 0, 0, QTextStreamPrivate::NotSpace); + d->consumeLastToken(); + + const QChar *ptr; + int length; + if (!d->scan(&ptr, &length, 0, QTextStreamPrivate::Space)) { + setStatus(ReadPastEnd); + return *this; + } + + str = QString(ptr, length); + d->consumeLastToken(); + return *this; +} + +/*! + \overload + + Converts the word to ISO-8859-1, then stores it in \a array. + + \sa QString::toLatin1() +*/ +QTextStream &QTextStream::operator>>(QByteArray &array) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + + array.clear(); + d->scan(0, 0, 0, QTextStreamPrivate::NotSpace); + d->consumeLastToken(); + + const QChar *ptr; + int length; + if (!d->scan(&ptr, &length, 0, QTextStreamPrivate::Space)) { + setStatus(ReadPastEnd); + return *this; + } + + for (int i = 0; i < length; ++i) + array += ptr[i].toLatin1(); + + d->consumeLastToken(); + return *this; +} + +/*! + \overload + + Stores the word in \a c, terminated by a '\0' character. If no word is + available, only the '\0' character is stored. + + Warning: Although convenient, this operator is dangerous and must + be used with care. QTextStream assumes that \a c points to a + buffer with enough space to hold the word. If the buffer is too + small, your application may crash. + + If possible, use the QByteArray operator instead. +*/ +QTextStream &QTextStream::operator>>(char *c) +{ + Q_D(QTextStream); + *c = 0; + CHECK_VALID_STREAM(*this); + d->scan(0, 0, 0, QTextStreamPrivate::NotSpace); + d->consumeLastToken(); + + const QChar *ptr; + int length; + if (!d->scan(&ptr, &length, 0, QTextStreamPrivate::Space)) { + setStatus(ReadPastEnd); + return *this; + } + + for (int i = 0; i < length; ++i) + *c++ = ptr[i].toLatin1(); + *c = '\0'; + d->consumeLastToken(); + return *this; +} + +/*! \internal + */ +void QTextStreamPrivate::putNumber(qulonglong number, bool negative) +{ + QString result; + + unsigned flags = 0; + if (numberFlags & QTextStream::ShowBase) + flags |= QLocalePrivate::ShowBase; + if (numberFlags & QTextStream::ForceSign) + flags |= QLocalePrivate::AlwaysShowSign; + if (numberFlags & QTextStream::UppercaseBase) + flags |= QLocalePrivate::UppercaseBase; + if (numberFlags & QTextStream::UppercaseDigits) + flags |= QLocalePrivate::CapitalEorX; + + // add thousands group separators. For backward compatibility we + // don't add a group separator for C locale. + if (locale != QLocale::c()) + flags |= QLocalePrivate::ThousandsGroup; + + const QLocalePrivate *dd = locale.d(); + int base = integerBase ? integerBase : 10; + if (negative && base == 10) { + result = dd->longLongToString(-static_cast<qlonglong>(number), -1, + base, -1, flags); + } else if (negative) { + // Workaround for backward compatibility for writing negative + // numbers in octal and hex: + // QTextStream(result) << showbase << hex << -1 << oct << -1 + // should output: -0x1 -0b1 + result = dd->unsLongLongToString(number, -1, base, -1, flags); + result.prepend(locale.negativeSign()); + } else { + result = dd->unsLongLongToString(number, -1, base, -1, flags); + // workaround for backward compatibility - in octal form with + // ShowBase flag set zero should be written as '00' + if (number == 0 && base == 8 && numberFlags & QTextStream::ShowBase + && result == QLatin1String("0")) { + result.prepend(QLatin1Char('0')); + } + } + putString(result, true); +} + +/*! + \internal + \overload +*/ +QTextStream &QTextStream::operator<<(QBool b) +{ + return *this << bool(b); +} + +/*! + Writes the character \a c to the stream, then returns a reference + to the QTextStream. + + \sa setFieldWidth() +*/ +QTextStream &QTextStream::operator<<(QChar c) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putString(QString(c)); + return *this; +} + +/*! + \overload + + Converts \a c from ASCII to a QChar, then writes it to the stream. +*/ +QTextStream &QTextStream::operator<<(char c) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putString(QString(QChar::fromAscii(c))); + return *this; +} + +/*! + Writes the integer number \a i to the stream, then returns a + reference to the QTextStream. By default, the number is stored in + decimal form, but you can also set the base by calling + setIntegerBase(). + + \sa setFieldWidth(), setNumberFlags() +*/ +QTextStream &QTextStream::operator<<(signed short i) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putNumber((qulonglong)qAbs(qlonglong(i)), i < 0); + return *this; +} + +/*! + \overload + + Writes the unsigned short \a i to the stream. +*/ +QTextStream &QTextStream::operator<<(unsigned short i) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putNumber((qulonglong)i, false); + return *this; +} + +/*! + \overload + + Writes the signed int \a i to the stream. +*/ +QTextStream &QTextStream::operator<<(signed int i) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putNumber((qulonglong)qAbs(qlonglong(i)), i < 0); + return *this; +} + +/*! + \overload + + Writes the unsigned int \a i to the stream. +*/ +QTextStream &QTextStream::operator<<(unsigned int i) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putNumber((qulonglong)i, false); + return *this; +} + +/*! + \overload + + Writes the signed long \a i to the stream. +*/ +QTextStream &QTextStream::operator<<(signed long i) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putNumber((qulonglong)qAbs(qlonglong(i)), i < 0); + return *this; +} + +/*! + \overload + + Writes the unsigned long \a i to the stream. +*/ +QTextStream &QTextStream::operator<<(unsigned long i) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putNumber((qulonglong)i, false); + return *this; +} + +/*! + \overload + + Writes the qlonglong \a i to the stream. +*/ +QTextStream &QTextStream::operator<<(qlonglong i) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putNumber((qulonglong)qAbs(i), i < 0); + return *this; +} + +/*! + \overload + + Writes the qulonglong \a i to the stream. +*/ +QTextStream &QTextStream::operator<<(qulonglong i) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putNumber(i, false); + return *this; +} + +/*! + Writes the real number \a f to the stream, then returns a + reference to the QTextStream. By default, QTextStream stores it + using SmartNotation, with up to 6 digits of precision. You can + change the textual representation QTextStream will use for real + numbers by calling setRealNumberNotation(), + setRealNumberPrecision() and setNumberFlags(). + + \sa setFieldWidth(), setRealNumberNotation(), + setRealNumberPrecision(), setNumberFlags() +*/ +QTextStream &QTextStream::operator<<(float f) +{ + return *this << double(f); +} + +/*! + \overload + + Writes the double \a f to the stream. +*/ +QTextStream &QTextStream::operator<<(double f) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + + QLocalePrivate::DoubleForm form = QLocalePrivate::DFDecimal; + switch (realNumberNotation()) { + case FixedNotation: + form = QLocalePrivate::DFDecimal; + break; + case ScientificNotation: + form = QLocalePrivate::DFExponent; + break; + case SmartNotation: + form = QLocalePrivate::DFSignificantDigits; + break; + } + + uint flags = 0; + if (numberFlags() & ShowBase) + flags |= QLocalePrivate::ShowBase; + if (numberFlags() & ForceSign) + flags |= QLocalePrivate::AlwaysShowSign; + if (numberFlags() & UppercaseBase) + flags |= QLocalePrivate::UppercaseBase; + if (numberFlags() & UppercaseDigits) + flags |= QLocalePrivate::CapitalEorX; + if (numberFlags() & ForcePoint) + flags |= QLocalePrivate::Alternate; + + const QLocalePrivate *dd = d->locale.d(); + QString num = dd->doubleToString(f, d->realNumberPrecision, form, -1, flags); + d->putString(num, true); + return *this; +} + +/*! + Writes the string \a string to the stream, and returns a reference + to the QTextStream. The string is first encoded using the assigned + codec (the default codec is QTextCodec::codecForLocale()) before + it is written to the stream. + + \sa setFieldWidth(), setCodec() +*/ +QTextStream &QTextStream::operator<<(const QString &string) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putString(string); + return *this; +} + +/*! + \overload + + Writes \a array to the stream. The contents of \a array are + converted with QString::fromAscii(). +*/ +QTextStream &QTextStream::operator<<(const QByteArray &array) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putString(QString::fromAscii(array.constData(), array.length())); + return *this; +} + +/*! + \overload + + Writes the constant string pointed to by \a string to the stream. \a + string is assumed to be in ISO-8859-1 encoding. This operator + is convenient when working with constant string data. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 8 + + Warning: QTextStream assumes that \a string points to a string of + text, terminated by a '\0' character. If there is no terminating + '\0' character, your application may crash. +*/ +QTextStream &QTextStream::operator<<(const char *string) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putString(QLatin1String(string)); + return *this; +} + +/*! + \overload + + Writes \a ptr to the stream as a hexadecimal number with a base. +*/ + +QTextStream &QTextStream::operator<<(const void *ptr) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + int oldBase = d->integerBase; + NumberFlags oldFlags = d->numberFlags; + d->integerBase = 16; + d->numberFlags |= ShowBase; + d->putNumber(reinterpret_cast<quintptr>(ptr), false); + d->integerBase = oldBase; + d->numberFlags = oldFlags; + return *this; +} + +/*! + \relates QTextStream + + Calls QTextStream::setIntegerBase(2) on \a stream and returns \a + stream. + + \sa oct(), dec(), hex(), {QTextStream manipulators} +*/ +QTextStream &bin(QTextStream &stream) +{ + stream.setIntegerBase(2); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setIntegerBase(8) on \a stream and returns \a + stream. + + \sa bin(), dec(), hex(), {QTextStream manipulators} +*/ +QTextStream &oct(QTextStream &stream) +{ + stream.setIntegerBase(8); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setIntegerBase(10) on \a stream and returns \a + stream. + + \sa bin(), oct(), hex(), {QTextStream manipulators} +*/ +QTextStream &dec(QTextStream &stream) +{ + stream.setIntegerBase(10); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setIntegerBase(16) on \a stream and returns \a + stream. + + \note The hex modifier can only be used for writing to streams. + \sa bin(), oct(), dec(), {QTextStream manipulators} +*/ +QTextStream &hex(QTextStream &stream) +{ + stream.setIntegerBase(16); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setNumberFlags(QTextStream::numberFlags() | + QTextStream::ShowBase) on \a stream and returns \a stream. + + \sa noshowbase(), forcesign(), forcepoint(), {QTextStream manipulators} +*/ +QTextStream &showbase(QTextStream &stream) +{ + stream.setNumberFlags(stream.numberFlags() | QTextStream::ShowBase); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setNumberFlags(QTextStream::numberFlags() | + QTextStream::ForceSign) on \a stream and returns \a stream. + + \sa noforcesign(), forcepoint(), showbase(), {QTextStream manipulators} +*/ +QTextStream &forcesign(QTextStream &stream) +{ + stream.setNumberFlags(stream.numberFlags() | QTextStream::ForceSign); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setNumberFlags(QTextStream::numberFlags() | + QTextStream::ForcePoint) on \a stream and returns \a stream. + + \sa noforcepoint(), forcesign(), showbase(), {QTextStream manipulators} +*/ +QTextStream &forcepoint(QTextStream &stream) +{ + stream.setNumberFlags(stream.numberFlags() | QTextStream::ForcePoint); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setNumberFlags(QTextStream::numberFlags() & + ~QTextStream::ShowBase) on \a stream and returns \a stream. + + \sa showbase(), noforcesign(), noforcepoint(), {QTextStream manipulators} +*/ +QTextStream &noshowbase(QTextStream &stream) +{ + stream.setNumberFlags(stream.numberFlags() &= ~QTextStream::ShowBase); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setNumberFlags(QTextStream::numberFlags() & + ~QTextStream::ForceSign) on \a stream and returns \a stream. + + \sa forcesign(), noforcepoint(), noshowbase(), {QTextStream manipulators} +*/ +QTextStream &noforcesign(QTextStream &stream) +{ + stream.setNumberFlags(stream.numberFlags() &= ~QTextStream::ForceSign); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setNumberFlags(QTextStream::numberFlags() & + ~QTextStream::ForcePoint) on \a stream and returns \a stream. + + \sa forcepoint(), noforcesign(), noshowbase(), {QTextStream manipulators} +*/ +QTextStream &noforcepoint(QTextStream &stream) +{ + stream.setNumberFlags(stream.numberFlags() &= ~QTextStream::ForcePoint); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setNumberFlags(QTextStream::numberFlags() | + QTextStream::UppercaseBase) on \a stream and returns \a stream. + + \sa lowercasebase(), uppercasedigits(), {QTextStream manipulators} +*/ +QTextStream &uppercasebase(QTextStream &stream) +{ + stream.setNumberFlags(stream.numberFlags() | QTextStream::UppercaseBase); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setNumberFlags(QTextStream::numberFlags() | + QTextStream::UppercaseDigits) on \a stream and returns \a stream. + + \sa lowercasedigits(), uppercasebase(), {QTextStream manipulators} +*/ +QTextStream &uppercasedigits(QTextStream &stream) +{ + stream.setNumberFlags(stream.numberFlags() | QTextStream::UppercaseDigits); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setNumberFlags(QTextStream::numberFlags() & + ~QTextStream::UppercaseBase) on \a stream and returns \a stream. + + \sa uppercasebase(), lowercasedigits(), {QTextStream manipulators} +*/ +QTextStream &lowercasebase(QTextStream &stream) +{ + stream.setNumberFlags(stream.numberFlags() & ~QTextStream::UppercaseBase); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setNumberFlags(QTextStream::numberFlags() & + ~QTextStream::UppercaseDigits) on \a stream and returns \a stream. + + \sa uppercasedigits(), lowercasebase(), {QTextStream manipulators} +*/ +QTextStream &lowercasedigits(QTextStream &stream) +{ + stream.setNumberFlags(stream.numberFlags() & ~QTextStream::UppercaseDigits); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setRealNumberNotation(QTextStream::FixedNotation) + on \a stream and returns \a stream. + + \sa scientific(), {QTextStream manipulators} +*/ +QTextStream &fixed(QTextStream &stream) +{ + stream.setRealNumberNotation(QTextStream::FixedNotation); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setRealNumberNotation(QTextStream::ScientificNotation) + on \a stream and returns \a stream. + + \sa fixed(), {QTextStream manipulators} +*/ +QTextStream &scientific(QTextStream &stream) +{ + stream.setRealNumberNotation(QTextStream::ScientificNotation); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setFieldAlignment(QTextStream::AlignLeft) + on \a stream and returns \a stream. + + \sa right(), center(), {QTextStream manipulators} +*/ +QTextStream &left(QTextStream &stream) +{ + stream.setFieldAlignment(QTextStream::AlignLeft); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setFieldAlignment(QTextStream::AlignRight) + on \a stream and returns \a stream. + + \sa left(), center(), {QTextStream manipulators} +*/ +QTextStream &right(QTextStream &stream) +{ + stream.setFieldAlignment(QTextStream::AlignRight); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::setFieldAlignment(QTextStream::AlignCenter) + on \a stream and returns \a stream. + + \sa left(), right(), {QTextStream manipulators} +*/ +QTextStream ¢er(QTextStream &stream) +{ + stream.setFieldAlignment(QTextStream::AlignCenter); + return stream; +} + +/*! + \relates QTextStream + + Writes '\n' to the \a stream and flushes the stream. + + Equivalent to + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 9 + + Note: On Windows, all '\n' characters are written as '\r\n' if + QTextStream's device or string is opened using the QIODevice::Text flag. + + \sa flush(), reset(), {QTextStream manipulators} +*/ +QTextStream &endl(QTextStream &stream) +{ + return stream << QLatin1Char('\n') << flush; +} + +/*! + \relates QTextStream + + Calls QTextStream::flush() on \a stream and returns \a stream. + + \sa endl(), reset(), {QTextStream manipulators} +*/ +QTextStream &flush(QTextStream &stream) +{ + stream.flush(); + return stream; +} + +/*! + \relates QTextStream + + Calls QTextStream::reset() on \a stream and returns \a stream. + + \sa flush(), {QTextStream manipulators} +*/ +QTextStream &reset(QTextStream &stream) +{ + stream.reset(); + return stream; +} + +/*! + \relates QTextStream + + Calls skipWhiteSpace() on \a stream and returns \a stream. + + \sa {QTextStream manipulators} +*/ +QTextStream &ws(QTextStream &stream) +{ + stream.skipWhiteSpace(); + return stream; +} + +/*! + \fn QTextStreamManipulator qSetFieldWidth(int width) + \relates QTextStream + + Equivalent to QTextStream::setFieldWidth(\a width). +*/ + +/*! + \fn QTextStreamManipulator qSetPadChar(QChar ch) + \relates QTextStream + + Equivalent to QTextStream::setPadChar(\a ch). +*/ + +/*! + \fn QTextStreamManipulator qSetRealNumberPrecision(int precision) + \relates QTextStream + + Equivalent to QTextStream::setRealNumberPrecision(\a precision). +*/ + +#ifndef QT_NO_TEXTCODEC +/*! + \relates QTextStream + + Toggles insertion of the Byte Order Mark on \a stream when QTextStream is + used with a UTF codec. + + \sa QTextStream::setGenerateByteOrderMark(), {QTextStream manipulators} +*/ +QTextStream &bom(QTextStream &stream) +{ + stream.setGenerateByteOrderMark(true); + return stream; +} + +/*! + Sets the codec for this stream to \a codec. The codec is used for + decoding any data that is read from the assigned device, and for + encoding any data that is written. By default, + QTextCodec::codecForLocale() is used, and automatic unicode + detection is enabled. + + If QTextStream operates on a string, this function does nothing. + + \warning If you call this function while the text stream is reading + from an open sequential socket, the internal buffer may still contain + text decoded using the old codec. + + \sa codec(), setAutoDetectUnicode(), setLocale() +*/ +void QTextStream::setCodec(QTextCodec *codec) +{ + Q_D(QTextStream); + qint64 seekPos = -1; + if (!d->readBuffer.isEmpty()) { + if (!d->device->isSequential()) { + seekPos = pos(); + } + } + d->codec = codec; + if (seekPos >=0 && !d->readBuffer.isEmpty()) + seek(seekPos); +} + +/*! + Sets the codec for this stream to the QTextCodec for the encoding + specified by \a codecName. Common values for \c codecName include + "ISO 8859-1", "UTF-8", and "UTF-16". If the encoding isn't + recognized, nothing happens. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtextstream.cpp 10 + + \sa QTextCodec::codecForName(), setLocale() +*/ +void QTextStream::setCodec(const char *codecName) +{ + QTextCodec *codec = QTextCodec::codecForName(codecName); + if (codec) + setCodec(codec); +} + +/*! + Returns the codec that is current assigned to the stream. + + \sa setCodec(), setAutoDetectUnicode(), locale() +*/ +QTextCodec *QTextStream::codec() const +{ + Q_D(const QTextStream); + return d->codec; +} + +/*! + If \a enabled is true, QTextStream will attempt to detect Unicode + encoding by peeking into the stream data to see if it can find the + UTF-16 or UTF-32 BOM (Byte Order Mark). If this mark is found, QTextStream + will replace the current codec with the UTF codec. + + This function can be used together with setCodec(). It is common + to set the codec to UTF-8, and then enable UTF-16 detection. + + \sa autoDetectUnicode(), setCodec() +*/ +void QTextStream::setAutoDetectUnicode(bool enabled) +{ + Q_D(QTextStream); + d->autoDetectUnicode = enabled; +} + +/*! + Returns true if automatic Unicode detection is enabled, otherwise + returns false. Automatic Unicode detection is enabled by default. + + \sa setAutoDetectUnicode(), setCodec() +*/ +bool QTextStream::autoDetectUnicode() const +{ + Q_D(const QTextStream); + return d->autoDetectUnicode; +} + +/*! + If \a generate is true and a UTF codec is used, QTextStream will insert + the BOM (Byte Order Mark) before any data has been written to the + device. If \a generate is false, no BOM will be inserted. This function + must be called before any data is written. Otherwise, it does nothing. + + \sa generateByteOrderMark(), bom() +*/ +void QTextStream::setGenerateByteOrderMark(bool generate) +{ + Q_D(QTextStream); + if (d->writeBuffer.isEmpty()) { + if (generate) + d->writeConverterState.flags &= ~QTextCodec::IgnoreHeader; + else + d->writeConverterState.flags |= QTextCodec::IgnoreHeader; + } +} + +/*! + Returns true if QTextStream is set to generate the UTF BOM (Byte Order + Mark) when using a UTF codec; otherwise returns false. UTF BOM generation is + set to false by default. + + \sa setGenerateByteOrderMark() +*/ +bool QTextStream::generateByteOrderMark() const +{ + Q_D(const QTextStream); + return (d->writeConverterState.flags & QTextCodec::IgnoreHeader) == 0; +} + +#endif + +/*! + \since 4.5 + + Sets the locale for this stream to \a locale. The specified locale is + used for conversions between numbers and their string representations. + + The default locale is C and it is a special case - the thousands + group separator is not used for backward compatibility reasons. + + \sa locale() +*/ +void QTextStream::setLocale(const QLocale &locale) +{ + Q_D(QTextStream); + d->locale = locale; +} + +/*! + \since 4.5 + + Returns the locale for this stream. The default locale is C. + + \sa setLocale() +*/ +QLocale QTextStream::locale() const +{ + Q_D(const QTextStream); + return d->locale; +} + +#ifdef QT3_SUPPORT +/*! + \class QTextIStream + \brief The QTextIStream class is a convenience class for input streams. + + \compat + \reentrant + + Use QTextStream instead. +*/ + +/*! + \fn QTextIStream::QTextIStream(const QString *string) + + Use QTextStream(&\a{string}, QIODevice::ReadOnly) instead. +*/ +/*! + \fn QTextIStream::QTextIStream(QByteArray *byteArray) + + Use QTextStream(&\a{byteArray}, QIODevice::ReadOnly) instead. +*/ +/*! + \fn QTextIStream::QTextIStream(FILE *file) + + Use QTextStream(\a{file}, QIODevice::ReadOnly) instead. +*/ + +/*! + \class QTextOStream + \brief The QTextOStream class is a convenience class for output streams. + + \compat + \reentrant + + Use QTextStream instead. +*/ + +/*! + \fn QTextOStream::QTextOStream(QString *string) + + Use QTextStream(&\a{string}, QIODevice::WriteOnly) instead. +*/ +/*! + \fn QTextOStream::QTextOStream(QByteArray *byteArray) + + Use QTextStream(&\a{byteArray}, QIODevice::WriteOnly) instead. +*/ +/*! + \fn QTextOStream::QTextOStream(FILE *file) + + Use QTextStream(\a{file}, QIODevice::WriteOnly) instead. +*/ + +/*! \internal +*/ +int QTextStream::flagsInternal() const +{ + Q_D(const QTextStream); + + int f = 0; + switch (d->fieldAlignment) { + case AlignLeft: f |= left; break; + case AlignRight: f |= right; break; + case AlignCenter: f |= internal; break; + default: + break; + } + switch (d->integerBase) { + case 2: f |= bin; break; + case 8: f |= oct; break; + case 10: f |= dec; break; + case 16: f |= hex; break; + default: + break; + } + switch (d->realNumberNotation) { + case FixedNotation: f |= fixed; break; + case ScientificNotation: f |= scientific; break; + default: + break; + } + if (d->numberFlags & ShowBase) + f |= showbase; + if (d->numberFlags & ForcePoint) + f |= showpoint; + if (d->numberFlags & ForceSign) + f |= showpos; + if (d->numberFlags & UppercaseBase) + f |= uppercase; + return f; +} + +/*! \internal +*/ +int QTextStream::flagsInternal(int newFlags) +{ + int oldFlags = flagsInternal(); + + if (newFlags & left) + setFieldAlignment(AlignLeft); + else if (newFlags & right) + setFieldAlignment(AlignRight); + else if (newFlags & internal) + setFieldAlignment(AlignCenter); + + if (newFlags & bin) + setIntegerBase(2); + else if (newFlags & oct) + setIntegerBase(8); + else if (newFlags & dec) + setIntegerBase(10); + else if (newFlags & hex) + setIntegerBase(16); + + if (newFlags & showbase) + setNumberFlags(numberFlags() | ShowBase); + if (newFlags & showpos) + setNumberFlags(numberFlags() | ForceSign); + if (newFlags & showpoint) + setNumberFlags(numberFlags() | ForcePoint); + if (newFlags & uppercase) + setNumberFlags(numberFlags() | UppercaseBase); + + if (newFlags & fixed) + setRealNumberNotation(FixedNotation); + else if (newFlags & scientific) + setRealNumberNotation(ScientificNotation); + + return oldFlags; +} + +#ifndef QT_NO_TEXTCODEC +/*! + Use setCodec() and setAutoDetectUnicode() instead. +*/ +void QTextStream::setEncoding(Encoding encoding) +{ + Q_D(QTextStream); + resetCodecConverterStateHelper(&d->readConverterState); + resetCodecConverterStateHelper(&d->writeConverterState); + + switch (encoding) { + case Locale: + d->writeConverterState.flags |= QTextCodec::IgnoreHeader; + setCodec(QTextCodec::codecForLocale()); + d->autoDetectUnicode = true; + break; + case Latin1: + d->readConverterState.flags |= QTextCodec::IgnoreHeader; + d->writeConverterState.flags |= QTextCodec::IgnoreHeader; + setCodec(QTextCodec::codecForName("ISO-8859-1")); + d->autoDetectUnicode = false; + break; + case Unicode: + setCodec(QTextCodec::codecForName("UTF-16")); + d->autoDetectUnicode = false; + break; + case RawUnicode: + d->readConverterState.flags |= QTextCodec::IgnoreHeader; + d->writeConverterState.flags |= QTextCodec::IgnoreHeader; + setCodec(QTextCodec::codecForName("UTF-16")); + d->autoDetectUnicode = false; + break; + case UnicodeNetworkOrder: + d->readConverterState.flags |= QTextCodec::IgnoreHeader; + d->writeConverterState.flags |= QTextCodec::IgnoreHeader; + setCodec(QTextCodec::codecForName("UTF-16BE")); + d->autoDetectUnicode = false; + break; + case UnicodeReverse: + d->readConverterState.flags |= QTextCodec::IgnoreHeader; + d->writeConverterState.flags |= QTextCodec::IgnoreHeader; + setCodec(QTextCodec::codecForName("UTF-16LE")); + d->autoDetectUnicode = false; + break; + case UnicodeUTF8: + d->writeConverterState.flags |= QTextCodec::IgnoreHeader; + setCodec(QTextCodec::codecForName("UTF-8")); + d->autoDetectUnicode = true; + break; + } +} +#endif + +/*! + \enum QTextStream::Encoding + \compat + + \value Latin1 Use setCodec(QTextCodec::codecForName("ISO-8859-1")) instead. + \value Locale Use setCodec(QTextCodec::codecForLocale()) instead. + \value RawUnicode Use setCodec(QTextCodec::codecForName("UTF-16")) instead. + \value Unicode Use setCodec(QTextCodec::codecForName("UTF-16")) instead. + \value UnicodeNetworkOrder Use setCodec(QTextCodec::codecForName("UTF-16BE")) instead. + \value UnicodeReverse Use setCodec(QTextCodec::codecForName("UTF-16LE")) instead. + \value UnicodeUTF8 Use setCodec(QTextCodec::codecForName("UTF-8")) instead. + + Also, for all encodings except QTextStream::Latin1 and + QTextStream::UTF8, you need to call setAutoDetectUnicode(false) + to obtain the Qt 3 behavior in addition to the setCodec() call. + + \sa setCodec(), setAutoDetectUnicode() +*/ + +/*! + \fn int QTextStream::flags() const + + Use fieldAlignment(), padChar(), fieldWidth(), numberFlags(), + integerBase(), realNumberNotation(), and realNumberNotation + instead. +*/ + +/*! + \fn int QTextStream::flags(int) + + Use setFieldAlignment(), setPadChar(), setFieldWidth(), + setNumberFlags(), setIntegerBase(), setRealNumberNotation(), and + setRealNumberNotation instead. +*/ + +/*! + \fn int QTextStream::setf(int) + + Use setFieldAlignment(), setPadChar(), setFieldWidth(), + setNumberFlags(), setIntegerBase(), setRealNumberNotation(), and + setRealNumberNotation instead. +*/ + +/*! + \fn int QTextStream::setf(int, int) + + Use setFieldAlignment(), setPadChar(), setFieldWidth(), + setNumberFlags(), setIntegerBase(), setRealNumberNotation(), and + setRealNumberNotation instead. +*/ + +/*! + \fn int QTextStream::unsetf(int) + + Use setFieldAlignment(), setPadChar(), setFieldWidth(), + setNumberFlags(), setIntegerBase(), setRealNumberNotation(), and + setRealNumberNotation instead. +*/ + +/*! + \fn int QTextStream::width(int) + + Use setFieldWidth() instead. +*/ + +/*! + \fn int QTextStream::fill(int) + + Use setPadChar() instead. +*/ + +/*! + \fn int QTextStream::precision(int) + + Use setRealNumberPrecision() instead. +*/ + +/*! + \fn int QTextStream::read() + + Use readAll() or readLine() instead. +*/ + +/*! + \fn int QTextStream::unsetDevice() + + Use setDevice(0) instead. +*/ + +/*! + \variable QTextStream::skipws + \variable QTextStream::left + \variable QTextStream::right + \variable QTextStream::internal + \variable QTextStream::bin + \variable QTextStream::oct + \variable QTextStream::dec + \variable QTextStream::hex + \variable QTextStream::showbase + \variable QTextStream::showpoint + \variable QTextStream::uppercase + \variable QTextStream::showpos + \variable QTextStream::scientific + \variable QTextStream::fixed + \variable QTextStream::basefield + \variable QTextStream::adjustfield + \variable QTextStream::floatfield + \compat + + Use the new \l{QTextStream manipulators} instead. +*/ + +#endif + +QT_END_NAMESPACE + +#ifndef QT_NO_QOBJECT +#include "qtextstream.moc" +#endif + diff --git a/src/corelib/io/qtextstream.h b/src/corelib/io/qtextstream.h new file mode 100644 index 0000000000..fc2fee6ec9 --- /dev/null +++ b/src/corelib/io/qtextstream.h @@ -0,0 +1,377 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTSTREAM_H +#define QTEXTSTREAM_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qstring.h> +#include <QtCore/qchar.h> +#include <QtCore/qlocale.h> +#include <QtCore/qscopedpointer.h> + +#ifndef QT_NO_TEXTCODEC +# ifdef QT3_SUPPORT +# include <QtCore/qtextcodec.h> +# endif +#endif + +#include <stdio.h> + +#ifdef Status +#error qtextstream.h must be included before any header file that defines Status +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QTextCodec; +class QTextDecoder; + +class QTextStreamPrivate; +class Q_CORE_EXPORT QTextStream // text stream class +{ + Q_DECLARE_PRIVATE(QTextStream) + +public: + enum RealNumberNotation { + SmartNotation, + FixedNotation, + ScientificNotation + }; + enum FieldAlignment { + AlignLeft, + AlignRight, + AlignCenter, + AlignAccountingStyle + }; + enum Status { + Ok, + ReadPastEnd, + ReadCorruptData, + WriteFailed + }; + enum NumberFlag { + ShowBase = 0x1, + ForcePoint = 0x2, + ForceSign = 0x4, + UppercaseBase = 0x8, + UppercaseDigits = 0x10 + }; + Q_DECLARE_FLAGS(NumberFlags, NumberFlag) + + QTextStream(); + explicit QTextStream(QIODevice *device); + explicit QTextStream(FILE *fileHandle, QIODevice::OpenMode openMode = QIODevice::ReadWrite); + explicit QTextStream(QString *string, QIODevice::OpenMode openMode = QIODevice::ReadWrite); + explicit QTextStream(QByteArray *array, QIODevice::OpenMode openMode = QIODevice::ReadWrite); + explicit QTextStream(const QByteArray &array, QIODevice::OpenMode openMode = QIODevice::ReadOnly); + virtual ~QTextStream(); + +#ifndef QT_NO_TEXTCODEC + void setCodec(QTextCodec *codec); + void setCodec(const char *codecName); + QTextCodec *codec() const; + void setAutoDetectUnicode(bool enabled); + bool autoDetectUnicode() const; + void setGenerateByteOrderMark(bool generate); + bool generateByteOrderMark() const; +#endif + + void setLocale(const QLocale &locale); + QLocale locale() const; + + void setDevice(QIODevice *device); + QIODevice *device() const; + + void setString(QString *string, QIODevice::OpenMode openMode = QIODevice::ReadWrite); + QString *string() const; + + Status status() const; + void setStatus(Status status); + void resetStatus(); + + bool atEnd() const; + void reset(); + void flush(); + bool seek(qint64 pos); + qint64 pos() const; + + void skipWhiteSpace(); + + QString readLine(qint64 maxlen = 0); + QString readAll(); + QString read(qint64 maxlen); + + void setFieldAlignment(FieldAlignment alignment); + FieldAlignment fieldAlignment() const; + + void setPadChar(QChar ch); + QChar padChar() const; + + void setFieldWidth(int width); + int fieldWidth() const; + + void setNumberFlags(NumberFlags flags); + NumberFlags numberFlags() const; + + void setIntegerBase(int base); + int integerBase() const; + + void setRealNumberNotation(RealNumberNotation notation); + RealNumberNotation realNumberNotation() const; + + void setRealNumberPrecision(int precision); + int realNumberPrecision() const; + + QTextStream &operator>>(QChar &ch); + QTextStream &operator>>(char &ch); + QTextStream &operator>>(signed short &i); + QTextStream &operator>>(unsigned short &i); + QTextStream &operator>>(signed int &i); + QTextStream &operator>>(unsigned int &i); + QTextStream &operator>>(signed long &i); + QTextStream &operator>>(unsigned long &i); + QTextStream &operator>>(qlonglong &i); + QTextStream &operator>>(qulonglong &i); + QTextStream &operator>>(float &f); + QTextStream &operator>>(double &f); + QTextStream &operator>>(QString &s); + QTextStream &operator>>(QByteArray &array); + QTextStream &operator>>(char *c); + + QTextStream &operator<<(QBool b); + QTextStream &operator<<(QChar ch); + QTextStream &operator<<(char ch); + QTextStream &operator<<(signed short i); + QTextStream &operator<<(unsigned short i); + QTextStream &operator<<(signed int i); + QTextStream &operator<<(unsigned int i); + QTextStream &operator<<(signed long i); + QTextStream &operator<<(unsigned long i); + QTextStream &operator<<(qlonglong i); + QTextStream &operator<<(qulonglong i); + QTextStream &operator<<(float f); + QTextStream &operator<<(double f); + QTextStream &operator<<(const QString &s); + QTextStream &operator<<(const QByteArray &array); + QTextStream &operator<<(const char *c); + QTextStream &operator<<(const void *ptr); + +#ifdef QT3_SUPPORT + // not marked as QT3_SUPPORT to avoid double compiler warnings, as + // they are used in the QT3_SUPPORT functions below. + inline QT3_SUPPORT int flags() const { return flagsInternal(); } + inline QT3_SUPPORT int flags(int f) { return flagsInternal(f); } + + inline QT3_SUPPORT int setf(int bits) + { int old = flagsInternal(); flagsInternal(flagsInternal() | bits); return old; } + inline QT3_SUPPORT int setf(int bits, int mask) + { int old = flagsInternal(); flagsInternal(flagsInternal() | (bits & mask)); return old; } + inline QT3_SUPPORT int unsetf(int bits) + { int old = flagsInternal(); flagsInternal(flagsInternal() & ~bits); return old; } + + inline QT3_SUPPORT int width(int w) + { int old = fieldWidth(); setFieldWidth(w); return old; } + inline QT3_SUPPORT int fill(int f) + { QChar ch = padChar(); setPadChar(QChar(f)); return ch.unicode(); } + inline QT3_SUPPORT int precision(int p) + { int old = realNumberPrecision(); setRealNumberPrecision(p); return old; } + + enum { + skipws = 0x0001, // skip whitespace on input + left = 0x0002, // left-adjust output + right = 0x0004, // right-adjust output + internal = 0x0008, // pad after sign + bin = 0x0010, // binary format integer + oct = 0x0020, // octal format integer + dec = 0x0040, // decimal format integer + hex = 0x0080, // hex format integer + showbase = 0x0100, // show base indicator + showpoint = 0x0200, // force decimal point (float) + uppercase = 0x0400, // upper-case hex output + showpos = 0x0800, // add '+' to positive integers + scientific = 0x1000, // scientific float output + fixed = 0x2000 // fixed float output + }; + enum { + basefield = bin | oct | dec | hex, + adjustfield = left | right | internal, + floatfield = scientific | fixed + }; + +#ifndef QT_NO_TEXTCODEC + enum Encoding { Locale, Latin1, Unicode, UnicodeNetworkOrder, + UnicodeReverse, RawUnicode, UnicodeUTF8 }; + QT3_SUPPORT void setEncoding(Encoding encoding); +#endif + inline QT3_SUPPORT QString read() { return readAll(); } + inline QT3_SUPPORT void unsetDevice() { setDevice(0); } +#endif + +private: +#ifdef QT3_SUPPORT + int flagsInternal() const; + int flagsInternal(int flags); +#endif + + Q_DISABLE_COPY(QTextStream) + + QScopedPointer<QTextStreamPrivate> d_ptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTextStream::NumberFlags) + +/***************************************************************************** + QTextStream manipulators + *****************************************************************************/ + +typedef QTextStream & (*QTextStreamFunction)(QTextStream &);// manipulator function +typedef void (QTextStream::*QTSMFI)(int); // manipulator w/int argument +typedef void (QTextStream::*QTSMFC)(QChar); // manipulator w/QChar argument + +class Q_CORE_EXPORT QTextStreamManipulator +{ +public: + QTextStreamManipulator(QTSMFI m, int a) { mf = m; mc = 0; arg = a; } + QTextStreamManipulator(QTSMFC m, QChar c) { mf = 0; mc = m; ch = c; arg = -1; } + void exec(QTextStream &s) { if (mf) { (s.*mf)(arg); } else { (s.*mc)(ch); } } + +private: + QTSMFI mf; // QTextStream member function + QTSMFC mc; // QTextStream member function + int arg; // member function argument + QChar ch; +}; + +inline QTextStream &operator>>(QTextStream &s, QTextStreamFunction f) +{ return (*f)(s); } + +inline QTextStream &operator<<(QTextStream &s, QTextStreamFunction f) +{ return (*f)(s); } + +inline QTextStream &operator<<(QTextStream &s, QTextStreamManipulator m) +{ m.exec(s); return s; } + +Q_CORE_EXPORT QTextStream &bin(QTextStream &s); +Q_CORE_EXPORT QTextStream &oct(QTextStream &s); +Q_CORE_EXPORT QTextStream &dec(QTextStream &s); +Q_CORE_EXPORT QTextStream &hex(QTextStream &s); + +Q_CORE_EXPORT QTextStream &showbase(QTextStream &s); +Q_CORE_EXPORT QTextStream &forcesign(QTextStream &s); +Q_CORE_EXPORT QTextStream &forcepoint(QTextStream &s); +Q_CORE_EXPORT QTextStream &noshowbase(QTextStream &s); +Q_CORE_EXPORT QTextStream &noforcesign(QTextStream &s); +Q_CORE_EXPORT QTextStream &noforcepoint(QTextStream &s); + +Q_CORE_EXPORT QTextStream &uppercasebase(QTextStream &s); +Q_CORE_EXPORT QTextStream &uppercasedigits(QTextStream &s); +Q_CORE_EXPORT QTextStream &lowercasebase(QTextStream &s); +Q_CORE_EXPORT QTextStream &lowercasedigits(QTextStream &s); + +Q_CORE_EXPORT QTextStream &fixed(QTextStream &s); +Q_CORE_EXPORT QTextStream &scientific(QTextStream &s); + +Q_CORE_EXPORT QTextStream &left(QTextStream &s); +Q_CORE_EXPORT QTextStream &right(QTextStream &s); +Q_CORE_EXPORT QTextStream ¢er(QTextStream &s); + +Q_CORE_EXPORT QTextStream &endl(QTextStream &s); +Q_CORE_EXPORT QTextStream &flush(QTextStream &s); +Q_CORE_EXPORT QTextStream &reset(QTextStream &s); + +Q_CORE_EXPORT QTextStream &bom(QTextStream &s); + +Q_CORE_EXPORT QTextStream &ws(QTextStream &s); + +inline QTextStreamManipulator qSetFieldWidth(int width) +{ + QTSMFI func = &QTextStream::setFieldWidth; + return QTextStreamManipulator(func,width); +} + +inline QTextStreamManipulator qSetPadChar(QChar ch) +{ + QTSMFC func = &QTextStream::setPadChar; + return QTextStreamManipulator(func, ch); +} + +inline QTextStreamManipulator qSetRealNumberPrecision(int precision) +{ + QTSMFI func = &QTextStream::setRealNumberPrecision; + return QTextStreamManipulator(func, precision); +} + +#ifdef QT3_SUPPORT +typedef QTextStream QTS; + +class Q_CORE_EXPORT QTextIStream : public QTextStream +{ +public: + inline explicit QTextIStream(const QString *s) : QTextStream(const_cast<QString *>(s), QIODevice::ReadOnly) {} + inline explicit QTextIStream(QByteArray *a) : QTextStream(a, QIODevice::ReadOnly) {} + inline QTextIStream(FILE *f) : QTextStream(f, QIODevice::ReadOnly) {} + +private: + Q_DISABLE_COPY(QTextIStream) +}; + +class Q_CORE_EXPORT QTextOStream : public QTextStream +{ +public: + inline explicit QTextOStream(QString *s) : QTextStream(s, QIODevice::WriteOnly) {} + inline explicit QTextOStream(QByteArray *a) : QTextStream(a, QIODevice::WriteOnly) {} + inline QTextOStream(FILE *f) : QTextStream(f, QIODevice::WriteOnly) {} + +private: + Q_DISABLE_COPY(QTextOStream) +}; +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEXTSTREAM_H diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp new file mode 100644 index 0000000000..efd3f45ef0 --- /dev/null +++ b/src/corelib/io/qurl.cpp @@ -0,0 +1,6544 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QUrl + + \brief The QUrl class provides a convenient interface for working + with URLs. + + \reentrant + \ingroup io + \ingroup network + \ingroup shared + + + It can parse and construct URLs in both encoded and unencoded + form. QUrl also has support for internationalized domain names + (IDNs). + + The most common way to use QUrl is to initialize it via the + constructor by passing a QString. Otherwise, setUrl() and + setEncodedUrl() can also be used. + + URLs can be represented in two forms: encoded or unencoded. The + unencoded representation is suitable for showing to users, but + the encoded representation is typically what you would send to + a web server. For example, the unencoded URL + "http://b\uuml\c{}hler.example.com" would be sent to the server as + "http://xn--bhler-kva.example.com/List%20of%20applicants.xml". + + A URL can also be constructed piece by piece by calling + setScheme(), setUserName(), setPassword(), setHost(), setPort(), + setPath(), setEncodedQuery() and setFragment(). Some convenience + functions are also available: setAuthority() sets the user name, + password, host and port. setUserInfo() sets the user name and + password at once. + + Call isValid() to check if the URL is valid. This can be done at + any point during the constructing of a URL. + + Constructing a query is particularly convenient through the use + of setQueryItems(), addQueryItem() and removeQueryItem(). Use + setQueryDelimiters() to customize the delimiters used for + generating the query string. + + For the convenience of generating encoded URL strings or query + strings, there are two static functions called + fromPercentEncoding() and toPercentEncoding() which deal with + percent encoding and decoding of QStrings. + + Calling isRelative() will tell whether or not the URL is + relative. A relative URL can be resolved by passing it as argument + to resolved(), which returns an absolute URL. isParentOf() is used + for determining whether one URL is a parent of another. + + fromLocalFile() constructs a QUrl by parsing a local + file path. toLocalFile() converts a URL to a local file path. + + The human readable representation of the URL is fetched with + toString(). This representation is appropriate for displaying a + URL to a user in unencoded form. The encoded form however, as + returned by toEncoded(), is for internal use, passing to web + servers, mail clients and so on. + + QUrl conforms to the URI specification from + \l{RFC 3986} (Uniform Resource Identifier: Generic Syntax), and includes + scheme extensions from \l{RFC 1738} (Uniform Resource Locators). Case + folding rules in QUrl conform to \l{RFC 3491} (Nameprep: A Stringprep + Profile for Internationalized Domain Names (IDN)). + + \sa QUrlInfo +*/ + +/*! + \enum QUrl::ParsingMode + + The parsing mode controls the way QUrl parses strings. + + \value TolerantMode QUrl will try to correct some common errors in URLs. + This mode is useful when processing URLs entered by + users. + + \value StrictMode Only valid URLs are accepted. This mode is useful for + general URL validation. + + In TolerantMode, the parser corrects the following invalid input: + + \list + + \o Spaces and "%20": If an encoded URL contains a space, this will be + replaced with "%20". If a decoded URL contains "%20", this will be + replaced with a single space before the URL is parsed. + + \o Single "%" characters: Any occurrences of a percent character "%" not + followed by exactly two hexadecimal characters (e.g., "13% coverage.html") + will be replaced by "%25". + + \o Reserved and unreserved characters: An encoded URL should only + contain a few characters as literals; all other characters should + be percent-encoded. In TolerantMode, these characters will be + automatically percent-encoded where they are not allowed: + space / double-quote / "<" / ">" / "[" / "\" / + "]" / "^" / "`" / "{" / "|" / "}" + + \endlist +*/ + +/*! + \enum QUrl::FormattingOption + + The formatting options define how the URL is formatted when written out + as text. + + \value None The format of the URL is unchanged. + \value RemoveScheme The scheme is removed from the URL. + \value RemovePassword Any password in the URL is removed. + \value RemoveUserInfo Any user information in the URL is removed. + \value RemovePort Any specified port is removed from the URL. + \value RemoveAuthority + \value RemovePath The URL's path is removed, leaving only the scheme, + host address, and port (if present). + \value RemoveQuery The query part of the URL (following a '?' character) + is removed. + \value RemoveFragment + \value StripTrailingSlash The trailing slash is removed if one is present. + + Note that the case folding rules in \l{RFC 3491}{Nameprep}, which QUrl + conforms to, require host names to always be converted to lower case, + regardless of the Qt::FormattingOptions used. +*/ + +/*! + \fn uint qHash(const QUrl &url) + \since 4.7 + \relates QUrl + + Computes a hash key from the normalized version of \a url. + */ +#include "qplatformdefs.h" +#include "qurl.h" +#include "qatomic.h" +#include "qbytearray.h" +#include "qdir.h" +#include "qfile.h" +#include "qlist.h" +#ifndef QT_NO_REGEXP +#include "qregexp.h" +#endif +#include "qstring.h" +#include "qstringlist.h" +#include "qstack.h" +#include "qvarlengtharray.h" +#include "qdebug.h" +#if defined QT3_SUPPORT +#include "qfileinfo.h" +#endif + +#if defined(Q_OS_WINCE_WM) +#pragma optimize("g", off) +#endif + +QT_BEGIN_NAMESPACE + +extern void q_normalizePercentEncoding(QByteArray *ba, const char *exclude); +extern void q_toPercentEncoding(QByteArray *ba, const char *exclude, const char *include = 0); +extern void q_fromPercentEncoding(QByteArray *ba); + +static QByteArray toPercentEncodingHelper(const QString &s, const char *exclude, const char *include = 0) +{ + if (s.isNull()) + return QByteArray(); // null + QByteArray ba = s.toUtf8(); + q_toPercentEncoding(&ba, exclude, include); + return ba; +} + +static QString fromPercentEncodingHelper(const QByteArray &ba) +{ + if (ba.isNull()) + return QString(); // null + QByteArray copy = ba; + q_fromPercentEncoding(©); + return QString::fromUtf8(copy.constData(), copy.length()); +} + +static QString fromPercentEncodingMutable(QByteArray *ba) +{ + if (ba->isNull()) + return QString(); // null + q_fromPercentEncoding(ba); + return QString::fromUtf8(ba->constData(), ba->length()); +} + +// ### Qt 5: Consider accepting empty strings as valid. See task 144227. + +//#define QURL_DEBUG + +// implemented in qvsnprintf.cpp +Q_CORE_EXPORT int qsnprintf(char *str, size_t n, const char *fmt, ...); + +// needed by the punycode encoder/decoder +#define Q_MAXINT ((uint)((uint)(-1)>>1)) +static const uint base = 36; +static const uint tmin = 1; +static const uint tmax = 26; +static const uint skew = 38; +static const uint damp = 700; +static const uint initial_bias = 72; +static const uint initial_n = 128; + +#define QURL_SETFLAG(a, b) { (a) |= (b); } +#define QURL_UNSETFLAG(a, b) { (a) &= ~(b); } +#define QURL_HASFLAG(a, b) (((a) & (b)) == (b)) + +struct QUrlErrorInfo { + inline QUrlErrorInfo() : _source(0), _message(0), _expected(0), _found(0) + { } + + const char *_source; + const char *_message; + char _expected; + char _found; + + inline void setParams(const char *source, const char *message, char expected, char found) + { + _source = source; + _message = message; + _expected = expected; + _found = found; + } +}; + +struct QUrlParseData +{ + const char *scheme; + int schemeLength; + + const char *userInfo; + int userInfoDelimIndex; + int userInfoLength; + + const char *host; + int hostLength; + int port; + + const char *path; + int pathLength; + const char *query; + int queryLength; + const char *fragment; + int fragmentLength; +}; + + +class QUrlPrivate +{ +public: + QUrlPrivate(); + QUrlPrivate(const QUrlPrivate &other); + + bool setUrl(const QString &url); + + QString canonicalHost() const; + void ensureEncodedParts() const; + QString authority(QUrl::FormattingOptions options = QUrl::None) const; + void setAuthority(const QString &auth); + void setUserInfo(const QString &userInfo); + QString userInfo(QUrl::FormattingOptions options = QUrl::None) const; + void setEncodedAuthority(const QByteArray &authority); + void setEncodedUserInfo(const QUrlParseData *parseData); + + QByteArray mergePaths(const QByteArray &relativePath) const; + + void queryItem(int pos, int *value, int *end); + + enum ParseOptions { + ParseAndSet, + ParseOnly + }; + + void validate() const; + void parse(ParseOptions parseOptions = ParseAndSet) const; + void clear(); + + QByteArray toEncoded(QUrl::FormattingOptions options = QUrl::None) const; + + QAtomicInt ref; + + QString scheme; + QString userName; + QString password; + QString host; + QString path; + QByteArray query; + QString fragment; + + QByteArray encodedOriginal; + QByteArray encodedUserName; + QByteArray encodedPassword; + QByteArray encodedPath; + QByteArray encodedFragment; + + int port; + QUrl::ParsingMode parsingMode; + + bool hasQuery; + bool hasFragment; + bool isValid; + bool isHostValid; + + char valueDelimiter; + char pairDelimiter; + + enum State { + Parsed = 0x1, + Validated = 0x2, + Normalized = 0x4, + HostCanonicalized = 0x8 + }; + int stateFlags; + + mutable QByteArray encodedNormalized; + const QByteArray & normalized() const; + + mutable QUrlErrorInfo errorInfo; + QString createErrorString(); +}; + + +static bool QT_FASTCALL _HEXDIG(const char **ptr) +{ + char ch = **ptr; + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) { + ++(*ptr); + return true; + } + + return false; +} + +// pct-encoded = "%" HEXDIG HEXDIG +static bool QT_FASTCALL _pctEncoded(const char **ptr) +{ + const char *ptrBackup = *ptr; + + if (**ptr != '%') + return false; + ++(*ptr); + + if (!_HEXDIG(ptr)) { + *ptr = ptrBackup; + return false; + } + if (!_HEXDIG(ptr)) { + *ptr = ptrBackup; + return false; + } + + return true; +} + +#if 0 +// gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" +static bool QT_FASTCALL _genDelims(const char **ptr, char *c) +{ + char ch = **ptr; + switch (ch) { + case ':': case '/': case '?': case '#': + case '[': case ']': case '@': + *c = ch; + ++(*ptr); + return true; + default: + return false; + } +} +#endif + +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" +// / "*" / "+" / "," / ";" / "=" +static bool QT_FASTCALL _subDelims(const char **ptr) +{ + char ch = **ptr; + switch (ch) { + case '!': case '$': case '&': case '\'': + case '(': case ')': case '*': case '+': + case ',': case ';': case '=': + ++(*ptr); + return true; + default: + return false; + } +} + +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +static bool QT_FASTCALL _unreserved(const char **ptr) +{ + char ch = **ptr; + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') + || (ch >= '0' && ch <= '9') + || ch == '-' || ch == '.' || ch == '_' || ch == '~') { + ++(*ptr); + return true; + } + return false; +} + +// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) +static bool QT_FASTCALL _scheme(const char **ptr, QUrlParseData *parseData) +{ + bool first = true; + bool isSchemeValid = true; + + parseData->scheme = *ptr; + for (;;) { + char ch = **ptr; + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + ; + } else if ((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.') { + if (first) + isSchemeValid = false; + } else { + break; + } + + ++(*ptr); + first = false; + } + + if (**ptr != ':') { + isSchemeValid = true; + *ptr = parseData->scheme; + } else { + parseData->schemeLength = *ptr - parseData->scheme; + ++(*ptr); // skip ':' + } + + return isSchemeValid; +} + +// IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) +static bool QT_FASTCALL _IPvFuture(const char **ptr) +{ + if (**ptr != 'v') + return false; + + const char *ptrBackup = *ptr; + ++(*ptr); + + if (!_HEXDIG(ptr)) { + *ptr = ptrBackup; + return false; + } + + while (_HEXDIG(ptr)) + ; + + if (**ptr != '.') { + *ptr = ptrBackup; + return false; + } + ++(*ptr); + + if (!_unreserved(ptr) && !_subDelims(ptr) && *((*ptr)++) != ':') { + *ptr = ptrBackup; + return false; + } + + + while (_unreserved(ptr) || _subDelims(ptr) || *((*ptr)++) == ':') + ; + + return true; +} + +// h16 = 1*4HEXDIG +// ; 16 bits of address represented in hexadecimal +static bool QT_FASTCALL _h16(const char **ptr) +{ + int i = 0; + for (; i < 4; ++i) { + if (!_HEXDIG(ptr)) + break; + } + return (i != 0); +} + +// dec-octet = DIGIT ; 0-9 +// / %x31-39 DIGIT ; 10-99 +// / "1" 2DIGIT ; 100-199 +// / "2" %x30-34 DIGIT ; 200-249 +// / "25" %x30-35 ; 250-255 +static bool QT_FASTCALL _decOctet(const char **ptr) +{ + const char *ptrBackup = *ptr; + char c1 = **ptr; + + if (c1 < '0' || c1 > '9') + return false; + + ++(*ptr); + + if (c1 == '0') + return true; + + char c2 = **ptr; + + if (c2 < '0' || c2 > '9') + return true; + + ++(*ptr); + + char c3 = **ptr; + if (c3 < '0' || c3 > '9') + return true; + + // If there is a three digit number larger than 255, reject the + // whole token. + if (c1 >= '2' && c2 >= '5' && c3 > '5') { + *ptr = ptrBackup; + return false; + } + + ++(*ptr); + + return true; +} + +// IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet +static bool QT_FASTCALL _IPv4Address(const char **ptr) +{ + const char *ptrBackup = *ptr; + + if (!_decOctet(ptr)) { + *ptr = ptrBackup; + return false; + } + + for (int i = 0; i < 3; ++i) { + char ch = *((*ptr)++); + if (ch != '.') { + *ptr = ptrBackup; + return false; + } + + if (!_decOctet(ptr)) { + *ptr = ptrBackup; + return false; + } + } + + return true; +} + +// ls32 = ( h16 ":" h16 ) / IPv4address +// ; least-significant 32 bits of address +static bool QT_FASTCALL _ls32(const char **ptr) +{ + const char *ptrBackup = *ptr; + if (_h16(ptr) && *((*ptr)++) == ':' && _h16(ptr)) + return true; + + *ptr = ptrBackup; + return _IPv4Address(ptr); +} + +// IPv6address = 6( h16 ":" ) ls32 // case 1 +// / "::" 5( h16 ":" ) ls32 // case 2 +// / [ h16 ] "::" 4( h16 ":" ) ls32 // case 3 +// / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 // case 4 +// / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 // case 5 +// / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 // case 6 +// / [ *4( h16 ":" ) h16 ] "::" ls32 // case 7 +// / [ *5( h16 ":" ) h16 ] "::" h16 // case 8 +// / [ *6( h16 ":" ) h16 ] "::" // case 9 +static bool QT_FASTCALL _IPv6Address(const char **ptr) +{ + const char *ptrBackup = *ptr; + + // count of (h16 ":") to the left of and including :: + int leftHexColons = 0; + // count of (h16 ":") to the right of :: + int rightHexColons = 0; + + // first count the number of (h16 ":") on the left of :: + while (_h16(ptr)) { + + // an h16 not followed by a colon is considered an + // error. + if (**ptr != ':') { + *ptr = ptrBackup; + return false; + } + ++(*ptr); + ++leftHexColons; + + // check for case 1, the only time when there can be no :: + if (leftHexColons == 6 && _ls32(ptr)) { + return true; + } + } + + // check for case 2 where the address starts with a : + if (leftHexColons == 0 && *((*ptr)++) != ':') { + *ptr = ptrBackup; + return false; + } + + // check for the second colon in :: + if (*((*ptr)++) != ':') { + *ptr = ptrBackup; + return false; + } + + int canBeCase = -1; + bool ls32WasRead = false; + + const char *tmpBackup = *ptr; + + // count the number of (h16 ":") on the right of :: + for (;;) { + tmpBackup = *ptr; + if (!_h16(ptr)) { + if (!_ls32(ptr)) { + if (rightHexColons != 0) { + *ptr = ptrBackup; + return false; + } + + // the address ended with :: (case 9) + // only valid if 1 <= leftHexColons <= 7 + canBeCase = 9; + } else { + ls32WasRead = true; + } + break; + } + ++rightHexColons; + if (**ptr != ':') { + // no colon could mean that what was read as an h16 + // was in fact the first part of an ls32. we backtrack + // and retry. + const char *pb = *ptr; + *ptr = tmpBackup; + if (_ls32(ptr)) { + ls32WasRead = true; + --rightHexColons; + } else { + *ptr = pb; + // address ends with only 1 h16 after :: (case 8) + if (rightHexColons == 1) + canBeCase = 8; + } + break; + } + ++(*ptr); + } + + // determine which case it is based on the number of rightHexColons + if (canBeCase == -1) { + + // check if a ls32 was read. If it wasn't and rightHexColons >= 2 then the + // last 2 HexColons are in fact a ls32 + if (!ls32WasRead && rightHexColons >= 2) + rightHexColons -= 2; + + canBeCase = 7 - rightHexColons; + } + + // based on the case we need to check that the number of leftHexColons is valid + if (leftHexColons > (canBeCase - 2)) { + *ptr = ptrBackup; + return false; + } + + return true; +} + +// IP-literal = "[" ( IPv6address / IPvFuture ) "]" +static bool QT_FASTCALL _IPLiteral(const char **ptr) +{ + const char *ptrBackup = *ptr; + if (**ptr != '[') + return false; + ++(*ptr); + + if (!_IPv6Address(ptr) && !_IPvFuture(ptr)) { + *ptr = ptrBackup; + return false; + } + + if (**ptr != ']') { + *ptr = ptrBackup; + return false; + } + ++(*ptr); + + return true; +} + +// reg-name = *( unreserved / pct-encoded / sub-delims ) +static void QT_FASTCALL _regName(const char **ptr) +{ + for (;;) { + if (!_unreserved(ptr) && !_subDelims(ptr)) { + if (!_pctEncoded(ptr)) + break; + } + } +} + +// host = IP-literal / IPv4address / reg-name +static void QT_FASTCALL _host(const char **ptr, QUrlParseData *parseData) +{ + parseData->host = *ptr; + if (!_IPLiteral(ptr)) { + if (_IPv4Address(ptr)) { + char ch = **ptr; + if (ch && ch != ':' && ch != '/') { + // reset + *ptr = parseData->host; + _regName(ptr); + } + } else { + _regName(ptr); + } + } + parseData->hostLength = *ptr - parseData->host; +} + +// userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) +static void QT_FASTCALL _userInfo(const char **ptr, QUrlParseData *parseData) +{ + parseData->userInfo = *ptr; + for (;;) { + if (_unreserved(ptr) || _subDelims(ptr)) { + ; + } else { + if (_pctEncoded(ptr)) { + ; + } else if (**ptr == ':') { + parseData->userInfoDelimIndex = *ptr - parseData->userInfo; + ++(*ptr); + } else { + break; + } + } + } + if (**ptr != '@') { + *ptr = parseData->userInfo; + parseData->userInfoDelimIndex = -1; + return; + } + parseData->userInfoLength = *ptr - parseData->userInfo; + ++(*ptr); +} + +// port = *DIGIT +static void QT_FASTCALL _port(const char **ptr, int *port) +{ + bool first = true; + + for (;;) { + const char *ptrBackup = *ptr; + char ch = *((*ptr)++); + if (ch < '0' || ch > '9') { + *ptr = ptrBackup; + break; + } + + if (first) { + first = false; + *port = 0; + } + + *port *= 10; + *port += ch - '0'; + } +} + +// authority = [ userinfo "@" ] host [ ":" port ] +static void QT_FASTCALL _authority(const char **ptr, QUrlParseData *parseData) +{ + _userInfo(ptr, parseData); + _host(ptr, parseData); + + if (**ptr != ':') + return; + + ++(*ptr); + _port(ptr, &parseData->port); +} + +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +static bool QT_FASTCALL _pchar(const char **ptr) +{ + char c = *(*ptr); + + switch (c) { + case '!': case '$': case '&': case '\'': case '(': case ')': case '*': + case '+': case ',': case ';': case '=': case ':': case '@': + case '-': case '.': case '_': case '~': + ++(*ptr); + return true; + default: + break; + }; + + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { + ++(*ptr); + return true; + } + + if (_pctEncoded(ptr)) + return true; + + return false; +} + +// segment = *pchar +static bool QT_FASTCALL _segmentNZ(const char **ptr) +{ + if (!_pchar(ptr)) + return false; + + while(_pchar(ptr)) + ; + + return true; +} + +// path-abempty = *( "/" segment ) +static void QT_FASTCALL _pathAbEmpty(const char **ptr) +{ + for (;;) { + if (**ptr != '/') + break; + ++(*ptr); + + while (_pchar(ptr)) + ; + } +} + +// path-abs = "/" [ segment-nz *( "/" segment ) ] +static bool QT_FASTCALL _pathAbs(const char **ptr) +{ + // **ptr == '/' already checked in caller + ++(*ptr); + + // we might be able to unnest this to gain some performance. + if (!_segmentNZ(ptr)) + return true; + + _pathAbEmpty(ptr); + + return true; +} + +// path-rootless = segment-nz *( "/" segment ) +static bool QT_FASTCALL _pathRootless(const char **ptr) +{ + // we might be able to unnest this to gain some performance. + if (!_segmentNZ(ptr)) + return false; + + _pathAbEmpty(ptr); + + return true; +} + + +// hier-part = "//" authority path-abempty +// / path-abs +// / path-rootless +// / path-empty +static void QT_FASTCALL _hierPart(const char **ptr, QUrlParseData *parseData) +{ + const char *ptrBackup = *ptr; + const char *pathStart = 0; + if (*((*ptr)++) == '/' && *((*ptr)++) == '/') { + _authority(ptr, parseData); + pathStart = *ptr; + _pathAbEmpty(ptr); + } else { + *ptr = ptrBackup; + pathStart = *ptr; + if (**ptr == '/') + _pathAbs(ptr); + else + _pathRootless(ptr); + } + parseData->path = pathStart; + parseData->pathLength = *ptr - pathStart; +} + +// query = *( pchar / "/" / "?" ) +static void QT_FASTCALL _query(const char **ptr, QUrlParseData *parseData) +{ + parseData->query = *ptr; + for (;;) { + if (_pchar(ptr)) { + ; + } else if (**ptr == '/' || **ptr == '?') { + ++(*ptr); + } else { + break; + } + } + parseData->queryLength = *ptr - parseData->query; +} + +// fragment = *( pchar / "/" / "?" ) +static void QT_FASTCALL _fragment(const char **ptr, QUrlParseData *parseData) +{ + parseData->fragment = *ptr; + for (;;) { + if (_pchar(ptr)) { + ; + } else if (**ptr == '/' || **ptr == '?' || **ptr == '#') { + ++(*ptr); + } else { + break; + } + } + parseData->fragmentLength = *ptr - parseData->fragment; +} + +struct NameprepCaseFoldingEntry { + uint uc; + ushort mapping[4]; +}; + +inline bool operator<(uint one, const NameprepCaseFoldingEntry &other) +{ return one < other.uc; } + +inline bool operator<(const NameprepCaseFoldingEntry &one, uint other) +{ return one.uc < other; } + +static const NameprepCaseFoldingEntry NameprepCaseFolding[] = { +/* { 0x0041, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x0042, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x0043, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x0044, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x0045, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x0046, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x0047, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x0048, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x0049, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x004A, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x004B, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x004C, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x004D, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x004E, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x004F, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x0050, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x0051, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x0052, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x0053, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x0054, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x0055, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x0056, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x0057, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x0058, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x0059, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x005A, { 0x007A, 0x0000, 0x0000, 0x0000 } },*/ + { 0x00B5, { 0x03BC, 0x0000, 0x0000, 0x0000 } }, + { 0x00C0, { 0x00E0, 0x0000, 0x0000, 0x0000 } }, + { 0x00C1, { 0x00E1, 0x0000, 0x0000, 0x0000 } }, + { 0x00C2, { 0x00E2, 0x0000, 0x0000, 0x0000 } }, + { 0x00C3, { 0x00E3, 0x0000, 0x0000, 0x0000 } }, + { 0x00C4, { 0x00E4, 0x0000, 0x0000, 0x0000 } }, + { 0x00C5, { 0x00E5, 0x0000, 0x0000, 0x0000 } }, + { 0x00C6, { 0x00E6, 0x0000, 0x0000, 0x0000 } }, + { 0x00C7, { 0x00E7, 0x0000, 0x0000, 0x0000 } }, + { 0x00C8, { 0x00E8, 0x0000, 0x0000, 0x0000 } }, + { 0x00C9, { 0x00E9, 0x0000, 0x0000, 0x0000 } }, + { 0x00CA, { 0x00EA, 0x0000, 0x0000, 0x0000 } }, + { 0x00CB, { 0x00EB, 0x0000, 0x0000, 0x0000 } }, + { 0x00CC, { 0x00EC, 0x0000, 0x0000, 0x0000 } }, + { 0x00CD, { 0x00ED, 0x0000, 0x0000, 0x0000 } }, + { 0x00CE, { 0x00EE, 0x0000, 0x0000, 0x0000 } }, + { 0x00CF, { 0x00EF, 0x0000, 0x0000, 0x0000 } }, + { 0x00D0, { 0x00F0, 0x0000, 0x0000, 0x0000 } }, + { 0x00D1, { 0x00F1, 0x0000, 0x0000, 0x0000 } }, + { 0x00D2, { 0x00F2, 0x0000, 0x0000, 0x0000 } }, + { 0x00D3, { 0x00F3, 0x0000, 0x0000, 0x0000 } }, + { 0x00D4, { 0x00F4, 0x0000, 0x0000, 0x0000 } }, + { 0x00D5, { 0x00F5, 0x0000, 0x0000, 0x0000 } }, + { 0x00D6, { 0x00F6, 0x0000, 0x0000, 0x0000 } }, + { 0x00D8, { 0x00F8, 0x0000, 0x0000, 0x0000 } }, + { 0x00D9, { 0x00F9, 0x0000, 0x0000, 0x0000 } }, + { 0x00DA, { 0x00FA, 0x0000, 0x0000, 0x0000 } }, + { 0x00DB, { 0x00FB, 0x0000, 0x0000, 0x0000 } }, + { 0x00DC, { 0x00FC, 0x0000, 0x0000, 0x0000 } }, + { 0x00DD, { 0x00FD, 0x0000, 0x0000, 0x0000 } }, + { 0x00DE, { 0x00FE, 0x0000, 0x0000, 0x0000 } }, + { 0x00DF, { 0x0073, 0x0073, 0x0000, 0x0000 } }, + { 0x0100, { 0x0101, 0x0000, 0x0000, 0x0000 } }, + { 0x0102, { 0x0103, 0x0000, 0x0000, 0x0000 } }, + { 0x0104, { 0x0105, 0x0000, 0x0000, 0x0000 } }, + { 0x0106, { 0x0107, 0x0000, 0x0000, 0x0000 } }, + { 0x0108, { 0x0109, 0x0000, 0x0000, 0x0000 } }, + { 0x010A, { 0x010B, 0x0000, 0x0000, 0x0000 } }, + { 0x010C, { 0x010D, 0x0000, 0x0000, 0x0000 } }, + { 0x010E, { 0x010F, 0x0000, 0x0000, 0x0000 } }, + { 0x0110, { 0x0111, 0x0000, 0x0000, 0x0000 } }, + { 0x0112, { 0x0113, 0x0000, 0x0000, 0x0000 } }, + { 0x0114, { 0x0115, 0x0000, 0x0000, 0x0000 } }, + { 0x0116, { 0x0117, 0x0000, 0x0000, 0x0000 } }, + { 0x0118, { 0x0119, 0x0000, 0x0000, 0x0000 } }, + { 0x011A, { 0x011B, 0x0000, 0x0000, 0x0000 } }, + { 0x011C, { 0x011D, 0x0000, 0x0000, 0x0000 } }, + { 0x011E, { 0x011F, 0x0000, 0x0000, 0x0000 } }, + { 0x0120, { 0x0121, 0x0000, 0x0000, 0x0000 } }, + { 0x0122, { 0x0123, 0x0000, 0x0000, 0x0000 } }, + { 0x0124, { 0x0125, 0x0000, 0x0000, 0x0000 } }, + { 0x0126, { 0x0127, 0x0000, 0x0000, 0x0000 } }, + { 0x0128, { 0x0129, 0x0000, 0x0000, 0x0000 } }, + { 0x012A, { 0x012B, 0x0000, 0x0000, 0x0000 } }, + { 0x012C, { 0x012D, 0x0000, 0x0000, 0x0000 } }, + { 0x012E, { 0x012F, 0x0000, 0x0000, 0x0000 } }, + { 0x0130, { 0x0069, 0x0307, 0x0000, 0x0000 } }, + { 0x0132, { 0x0133, 0x0000, 0x0000, 0x0000 } }, + { 0x0134, { 0x0135, 0x0000, 0x0000, 0x0000 } }, + { 0x0136, { 0x0137, 0x0000, 0x0000, 0x0000 } }, + { 0x0139, { 0x013A, 0x0000, 0x0000, 0x0000 } }, + { 0x013B, { 0x013C, 0x0000, 0x0000, 0x0000 } }, + { 0x013D, { 0x013E, 0x0000, 0x0000, 0x0000 } }, + { 0x013F, { 0x0140, 0x0000, 0x0000, 0x0000 } }, + { 0x0141, { 0x0142, 0x0000, 0x0000, 0x0000 } }, + { 0x0143, { 0x0144, 0x0000, 0x0000, 0x0000 } }, + { 0x0145, { 0x0146, 0x0000, 0x0000, 0x0000 } }, + { 0x0147, { 0x0148, 0x0000, 0x0000, 0x0000 } }, + { 0x0149, { 0x02BC, 0x006E, 0x0000, 0x0000 } }, + { 0x014A, { 0x014B, 0x0000, 0x0000, 0x0000 } }, + { 0x014C, { 0x014D, 0x0000, 0x0000, 0x0000 } }, + { 0x014E, { 0x014F, 0x0000, 0x0000, 0x0000 } }, + { 0x0150, { 0x0151, 0x0000, 0x0000, 0x0000 } }, + { 0x0152, { 0x0153, 0x0000, 0x0000, 0x0000 } }, + { 0x0154, { 0x0155, 0x0000, 0x0000, 0x0000 } }, + { 0x0156, { 0x0157, 0x0000, 0x0000, 0x0000 } }, + { 0x0158, { 0x0159, 0x0000, 0x0000, 0x0000 } }, + { 0x015A, { 0x015B, 0x0000, 0x0000, 0x0000 } }, + { 0x015C, { 0x015D, 0x0000, 0x0000, 0x0000 } }, + { 0x015E, { 0x015F, 0x0000, 0x0000, 0x0000 } }, + { 0x0160, { 0x0161, 0x0000, 0x0000, 0x0000 } }, + { 0x0162, { 0x0163, 0x0000, 0x0000, 0x0000 } }, + { 0x0164, { 0x0165, 0x0000, 0x0000, 0x0000 } }, + { 0x0166, { 0x0167, 0x0000, 0x0000, 0x0000 } }, + { 0x0168, { 0x0169, 0x0000, 0x0000, 0x0000 } }, + { 0x016A, { 0x016B, 0x0000, 0x0000, 0x0000 } }, + { 0x016C, { 0x016D, 0x0000, 0x0000, 0x0000 } }, + { 0x016E, { 0x016F, 0x0000, 0x0000, 0x0000 } }, + { 0x0170, { 0x0171, 0x0000, 0x0000, 0x0000 } }, + { 0x0172, { 0x0173, 0x0000, 0x0000, 0x0000 } }, + { 0x0174, { 0x0175, 0x0000, 0x0000, 0x0000 } }, + { 0x0176, { 0x0177, 0x0000, 0x0000, 0x0000 } }, + { 0x0178, { 0x00FF, 0x0000, 0x0000, 0x0000 } }, + { 0x0179, { 0x017A, 0x0000, 0x0000, 0x0000 } }, + { 0x017B, { 0x017C, 0x0000, 0x0000, 0x0000 } }, + { 0x017D, { 0x017E, 0x0000, 0x0000, 0x0000 } }, + { 0x017F, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x0181, { 0x0253, 0x0000, 0x0000, 0x0000 } }, + { 0x0182, { 0x0183, 0x0000, 0x0000, 0x0000 } }, + { 0x0184, { 0x0185, 0x0000, 0x0000, 0x0000 } }, + { 0x0186, { 0x0254, 0x0000, 0x0000, 0x0000 } }, + { 0x0187, { 0x0188, 0x0000, 0x0000, 0x0000 } }, + { 0x0189, { 0x0256, 0x0000, 0x0000, 0x0000 } }, + { 0x018A, { 0x0257, 0x0000, 0x0000, 0x0000 } }, + { 0x018B, { 0x018C, 0x0000, 0x0000, 0x0000 } }, + { 0x018E, { 0x01DD, 0x0000, 0x0000, 0x0000 } }, + { 0x018F, { 0x0259, 0x0000, 0x0000, 0x0000 } }, + { 0x0190, { 0x025B, 0x0000, 0x0000, 0x0000 } }, + { 0x0191, { 0x0192, 0x0000, 0x0000, 0x0000 } }, + { 0x0193, { 0x0260, 0x0000, 0x0000, 0x0000 } }, + { 0x0194, { 0x0263, 0x0000, 0x0000, 0x0000 } }, + { 0x0196, { 0x0269, 0x0000, 0x0000, 0x0000 } }, + { 0x0197, { 0x0268, 0x0000, 0x0000, 0x0000 } }, + { 0x0198, { 0x0199, 0x0000, 0x0000, 0x0000 } }, + { 0x019C, { 0x026F, 0x0000, 0x0000, 0x0000 } }, + { 0x019D, { 0x0272, 0x0000, 0x0000, 0x0000 } }, + { 0x019F, { 0x0275, 0x0000, 0x0000, 0x0000 } }, + { 0x01A0, { 0x01A1, 0x0000, 0x0000, 0x0000 } }, + { 0x01A2, { 0x01A3, 0x0000, 0x0000, 0x0000 } }, + { 0x01A4, { 0x01A5, 0x0000, 0x0000, 0x0000 } }, + { 0x01A6, { 0x0280, 0x0000, 0x0000, 0x0000 } }, + { 0x01A7, { 0x01A8, 0x0000, 0x0000, 0x0000 } }, + { 0x01A9, { 0x0283, 0x0000, 0x0000, 0x0000 } }, + { 0x01AC, { 0x01AD, 0x0000, 0x0000, 0x0000 } }, + { 0x01AE, { 0x0288, 0x0000, 0x0000, 0x0000 } }, + { 0x01AF, { 0x01B0, 0x0000, 0x0000, 0x0000 } }, + { 0x01B1, { 0x028A, 0x0000, 0x0000, 0x0000 } }, + { 0x01B2, { 0x028B, 0x0000, 0x0000, 0x0000 } }, + { 0x01B3, { 0x01B4, 0x0000, 0x0000, 0x0000 } }, + { 0x01B5, { 0x01B6, 0x0000, 0x0000, 0x0000 } }, + { 0x01B7, { 0x0292, 0x0000, 0x0000, 0x0000 } }, + { 0x01B8, { 0x01B9, 0x0000, 0x0000, 0x0000 } }, + { 0x01BC, { 0x01BD, 0x0000, 0x0000, 0x0000 } }, + { 0x01C4, { 0x01C6, 0x0000, 0x0000, 0x0000 } }, + { 0x01C5, { 0x01C6, 0x0000, 0x0000, 0x0000 } }, + { 0x01C7, { 0x01C9, 0x0000, 0x0000, 0x0000 } }, + { 0x01C8, { 0x01C9, 0x0000, 0x0000, 0x0000 } }, + { 0x01CA, { 0x01CC, 0x0000, 0x0000, 0x0000 } }, + { 0x01CB, { 0x01CC, 0x0000, 0x0000, 0x0000 } }, + { 0x01CD, { 0x01CE, 0x0000, 0x0000, 0x0000 } }, + { 0x01CF, { 0x01D0, 0x0000, 0x0000, 0x0000 } }, + { 0x01D1, { 0x01D2, 0x0000, 0x0000, 0x0000 } }, + { 0x01D3, { 0x01D4, 0x0000, 0x0000, 0x0000 } }, + { 0x01D5, { 0x01D6, 0x0000, 0x0000, 0x0000 } }, + { 0x01D7, { 0x01D8, 0x0000, 0x0000, 0x0000 } }, + { 0x01D9, { 0x01DA, 0x0000, 0x0000, 0x0000 } }, + { 0x01DB, { 0x01DC, 0x0000, 0x0000, 0x0000 } }, + { 0x01DE, { 0x01DF, 0x0000, 0x0000, 0x0000 } }, + { 0x01E0, { 0x01E1, 0x0000, 0x0000, 0x0000 } }, + { 0x01E2, { 0x01E3, 0x0000, 0x0000, 0x0000 } }, + { 0x01E4, { 0x01E5, 0x0000, 0x0000, 0x0000 } }, + { 0x01E6, { 0x01E7, 0x0000, 0x0000, 0x0000 } }, + { 0x01E8, { 0x01E9, 0x0000, 0x0000, 0x0000 } }, + { 0x01EA, { 0x01EB, 0x0000, 0x0000, 0x0000 } }, + { 0x01EC, { 0x01ED, 0x0000, 0x0000, 0x0000 } }, + { 0x01EE, { 0x01EF, 0x0000, 0x0000, 0x0000 } }, + { 0x01F0, { 0x006A, 0x030C, 0x0000, 0x0000 } }, + { 0x01F1, { 0x01F3, 0x0000, 0x0000, 0x0000 } }, + { 0x01F2, { 0x01F3, 0x0000, 0x0000, 0x0000 } }, + { 0x01F4, { 0x01F5, 0x0000, 0x0000, 0x0000 } }, + { 0x01F6, { 0x0195, 0x0000, 0x0000, 0x0000 } }, + { 0x01F7, { 0x01BF, 0x0000, 0x0000, 0x0000 } }, + { 0x01F8, { 0x01F9, 0x0000, 0x0000, 0x0000 } }, + { 0x01FA, { 0x01FB, 0x0000, 0x0000, 0x0000 } }, + { 0x01FC, { 0x01FD, 0x0000, 0x0000, 0x0000 } }, + { 0x01FE, { 0x01FF, 0x0000, 0x0000, 0x0000 } }, + { 0x0200, { 0x0201, 0x0000, 0x0000, 0x0000 } }, + { 0x0202, { 0x0203, 0x0000, 0x0000, 0x0000 } }, + { 0x0204, { 0x0205, 0x0000, 0x0000, 0x0000 } }, + { 0x0206, { 0x0207, 0x0000, 0x0000, 0x0000 } }, + { 0x0208, { 0x0209, 0x0000, 0x0000, 0x0000 } }, + { 0x020A, { 0x020B, 0x0000, 0x0000, 0x0000 } }, + { 0x020C, { 0x020D, 0x0000, 0x0000, 0x0000 } }, + { 0x020E, { 0x020F, 0x0000, 0x0000, 0x0000 } }, + { 0x0210, { 0x0211, 0x0000, 0x0000, 0x0000 } }, + { 0x0212, { 0x0213, 0x0000, 0x0000, 0x0000 } }, + { 0x0214, { 0x0215, 0x0000, 0x0000, 0x0000 } }, + { 0x0216, { 0x0217, 0x0000, 0x0000, 0x0000 } }, + { 0x0218, { 0x0219, 0x0000, 0x0000, 0x0000 } }, + { 0x021A, { 0x021B, 0x0000, 0x0000, 0x0000 } }, + { 0x021C, { 0x021D, 0x0000, 0x0000, 0x0000 } }, + { 0x021E, { 0x021F, 0x0000, 0x0000, 0x0000 } }, + { 0x0220, { 0x019E, 0x0000, 0x0000, 0x0000 } }, + { 0x0222, { 0x0223, 0x0000, 0x0000, 0x0000 } }, + { 0x0224, { 0x0225, 0x0000, 0x0000, 0x0000 } }, + { 0x0226, { 0x0227, 0x0000, 0x0000, 0x0000 } }, + { 0x0228, { 0x0229, 0x0000, 0x0000, 0x0000 } }, + { 0x022A, { 0x022B, 0x0000, 0x0000, 0x0000 } }, + { 0x022C, { 0x022D, 0x0000, 0x0000, 0x0000 } }, + { 0x022E, { 0x022F, 0x0000, 0x0000, 0x0000 } }, + { 0x0230, { 0x0231, 0x0000, 0x0000, 0x0000 } }, + { 0x0232, { 0x0233, 0x0000, 0x0000, 0x0000 } }, + { 0x0345, { 0x03B9, 0x0000, 0x0000, 0x0000 } }, + { 0x037A, { 0x0020, 0x03B9, 0x0000, 0x0000 } }, + { 0x0386, { 0x03AC, 0x0000, 0x0000, 0x0000 } }, + { 0x0388, { 0x03AD, 0x0000, 0x0000, 0x0000 } }, + { 0x0389, { 0x03AE, 0x0000, 0x0000, 0x0000 } }, + { 0x038A, { 0x03AF, 0x0000, 0x0000, 0x0000 } }, + { 0x038C, { 0x03CC, 0x0000, 0x0000, 0x0000 } }, + { 0x038E, { 0x03CD, 0x0000, 0x0000, 0x0000 } }, + { 0x038F, { 0x03CE, 0x0000, 0x0000, 0x0000 } }, + { 0x0390, { 0x03B9, 0x0308, 0x0301, 0x0000 } }, + { 0x0391, { 0x03B1, 0x0000, 0x0000, 0x0000 } }, + { 0x0392, { 0x03B2, 0x0000, 0x0000, 0x0000 } }, + { 0x0393, { 0x03B3, 0x0000, 0x0000, 0x0000 } }, + { 0x0394, { 0x03B4, 0x0000, 0x0000, 0x0000 } }, + { 0x0395, { 0x03B5, 0x0000, 0x0000, 0x0000 } }, + { 0x0396, { 0x03B6, 0x0000, 0x0000, 0x0000 } }, + { 0x0397, { 0x03B7, 0x0000, 0x0000, 0x0000 } }, + { 0x0398, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x0399, { 0x03B9, 0x0000, 0x0000, 0x0000 } }, + { 0x039A, { 0x03BA, 0x0000, 0x0000, 0x0000 } }, + { 0x039B, { 0x03BB, 0x0000, 0x0000, 0x0000 } }, + { 0x039C, { 0x03BC, 0x0000, 0x0000, 0x0000 } }, + { 0x039D, { 0x03BD, 0x0000, 0x0000, 0x0000 } }, + { 0x039E, { 0x03BE, 0x0000, 0x0000, 0x0000 } }, + { 0x039F, { 0x03BF, 0x0000, 0x0000, 0x0000 } }, + { 0x03A0, { 0x03C0, 0x0000, 0x0000, 0x0000 } }, + { 0x03A1, { 0x03C1, 0x0000, 0x0000, 0x0000 } }, + { 0x03A3, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x03A4, { 0x03C4, 0x0000, 0x0000, 0x0000 } }, + { 0x03A5, { 0x03C5, 0x0000, 0x0000, 0x0000 } }, + { 0x03A6, { 0x03C6, 0x0000, 0x0000, 0x0000 } }, + { 0x03A7, { 0x03C7, 0x0000, 0x0000, 0x0000 } }, + { 0x03A8, { 0x03C8, 0x0000, 0x0000, 0x0000 } }, + { 0x03A9, { 0x03C9, 0x0000, 0x0000, 0x0000 } }, + { 0x03AA, { 0x03CA, 0x0000, 0x0000, 0x0000 } }, + { 0x03AB, { 0x03CB, 0x0000, 0x0000, 0x0000 } }, + { 0x03B0, { 0x03C5, 0x0308, 0x0301, 0x0000 } }, + { 0x03C2, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x03D0, { 0x03B2, 0x0000, 0x0000, 0x0000 } }, + { 0x03D1, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x03D2, { 0x03C5, 0x0000, 0x0000, 0x0000 } }, + { 0x03D3, { 0x03CD, 0x0000, 0x0000, 0x0000 } }, + { 0x03D4, { 0x03CB, 0x0000, 0x0000, 0x0000 } }, + { 0x03D5, { 0x03C6, 0x0000, 0x0000, 0x0000 } }, + { 0x03D6, { 0x03C0, 0x0000, 0x0000, 0x0000 } }, + { 0x03D8, { 0x03D9, 0x0000, 0x0000, 0x0000 } }, + { 0x03DA, { 0x03DB, 0x0000, 0x0000, 0x0000 } }, + { 0x03DC, { 0x03DD, 0x0000, 0x0000, 0x0000 } }, + { 0x03DE, { 0x03DF, 0x0000, 0x0000, 0x0000 } }, + { 0x03E0, { 0x03E1, 0x0000, 0x0000, 0x0000 } }, + { 0x03E2, { 0x03E3, 0x0000, 0x0000, 0x0000 } }, + { 0x03E4, { 0x03E5, 0x0000, 0x0000, 0x0000 } }, + { 0x03E6, { 0x03E7, 0x0000, 0x0000, 0x0000 } }, + { 0x03E8, { 0x03E9, 0x0000, 0x0000, 0x0000 } }, + { 0x03EA, { 0x03EB, 0x0000, 0x0000, 0x0000 } }, + { 0x03EC, { 0x03ED, 0x0000, 0x0000, 0x0000 } }, + { 0x03EE, { 0x03EF, 0x0000, 0x0000, 0x0000 } }, + { 0x03F0, { 0x03BA, 0x0000, 0x0000, 0x0000 } }, + { 0x03F1, { 0x03C1, 0x0000, 0x0000, 0x0000 } }, + { 0x03F2, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x03F4, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x03F5, { 0x03B5, 0x0000, 0x0000, 0x0000 } }, + { 0x0400, { 0x0450, 0x0000, 0x0000, 0x0000 } }, + { 0x0401, { 0x0451, 0x0000, 0x0000, 0x0000 } }, + { 0x0402, { 0x0452, 0x0000, 0x0000, 0x0000 } }, + { 0x0403, { 0x0453, 0x0000, 0x0000, 0x0000 } }, + { 0x0404, { 0x0454, 0x0000, 0x0000, 0x0000 } }, + { 0x0405, { 0x0455, 0x0000, 0x0000, 0x0000 } }, + { 0x0406, { 0x0456, 0x0000, 0x0000, 0x0000 } }, + { 0x0407, { 0x0457, 0x0000, 0x0000, 0x0000 } }, + { 0x0408, { 0x0458, 0x0000, 0x0000, 0x0000 } }, + { 0x0409, { 0x0459, 0x0000, 0x0000, 0x0000 } }, + { 0x040A, { 0x045A, 0x0000, 0x0000, 0x0000 } }, + { 0x040B, { 0x045B, 0x0000, 0x0000, 0x0000 } }, + { 0x040C, { 0x045C, 0x0000, 0x0000, 0x0000 } }, + { 0x040D, { 0x045D, 0x0000, 0x0000, 0x0000 } }, + { 0x040E, { 0x045E, 0x0000, 0x0000, 0x0000 } }, + { 0x040F, { 0x045F, 0x0000, 0x0000, 0x0000 } }, + { 0x0410, { 0x0430, 0x0000, 0x0000, 0x0000 } }, + { 0x0411, { 0x0431, 0x0000, 0x0000, 0x0000 } }, + { 0x0412, { 0x0432, 0x0000, 0x0000, 0x0000 } }, + { 0x0413, { 0x0433, 0x0000, 0x0000, 0x0000 } }, + { 0x0414, { 0x0434, 0x0000, 0x0000, 0x0000 } }, + { 0x0415, { 0x0435, 0x0000, 0x0000, 0x0000 } }, + { 0x0416, { 0x0436, 0x0000, 0x0000, 0x0000 } }, + { 0x0417, { 0x0437, 0x0000, 0x0000, 0x0000 } }, + { 0x0418, { 0x0438, 0x0000, 0x0000, 0x0000 } }, + { 0x0419, { 0x0439, 0x0000, 0x0000, 0x0000 } }, + { 0x041A, { 0x043A, 0x0000, 0x0000, 0x0000 } }, + { 0x041B, { 0x043B, 0x0000, 0x0000, 0x0000 } }, + { 0x041C, { 0x043C, 0x0000, 0x0000, 0x0000 } }, + { 0x041D, { 0x043D, 0x0000, 0x0000, 0x0000 } }, + { 0x041E, { 0x043E, 0x0000, 0x0000, 0x0000 } }, + { 0x041F, { 0x043F, 0x0000, 0x0000, 0x0000 } }, + { 0x0420, { 0x0440, 0x0000, 0x0000, 0x0000 } }, + { 0x0421, { 0x0441, 0x0000, 0x0000, 0x0000 } }, + { 0x0422, { 0x0442, 0x0000, 0x0000, 0x0000 } }, + { 0x0423, { 0x0443, 0x0000, 0x0000, 0x0000 } }, + { 0x0424, { 0x0444, 0x0000, 0x0000, 0x0000 } }, + { 0x0425, { 0x0445, 0x0000, 0x0000, 0x0000 } }, + { 0x0426, { 0x0446, 0x0000, 0x0000, 0x0000 } }, + { 0x0427, { 0x0447, 0x0000, 0x0000, 0x0000 } }, + { 0x0428, { 0x0448, 0x0000, 0x0000, 0x0000 } }, + { 0x0429, { 0x0449, 0x0000, 0x0000, 0x0000 } }, + { 0x042A, { 0x044A, 0x0000, 0x0000, 0x0000 } }, + { 0x042B, { 0x044B, 0x0000, 0x0000, 0x0000 } }, + { 0x042C, { 0x044C, 0x0000, 0x0000, 0x0000 } }, + { 0x042D, { 0x044D, 0x0000, 0x0000, 0x0000 } }, + { 0x042E, { 0x044E, 0x0000, 0x0000, 0x0000 } }, + { 0x042F, { 0x044F, 0x0000, 0x0000, 0x0000 } }, + { 0x0460, { 0x0461, 0x0000, 0x0000, 0x0000 } }, + { 0x0462, { 0x0463, 0x0000, 0x0000, 0x0000 } }, + { 0x0464, { 0x0465, 0x0000, 0x0000, 0x0000 } }, + { 0x0466, { 0x0467, 0x0000, 0x0000, 0x0000 } }, + { 0x0468, { 0x0469, 0x0000, 0x0000, 0x0000 } }, + { 0x046A, { 0x046B, 0x0000, 0x0000, 0x0000 } }, + { 0x046C, { 0x046D, 0x0000, 0x0000, 0x0000 } }, + { 0x046E, { 0x046F, 0x0000, 0x0000, 0x0000 } }, + { 0x0470, { 0x0471, 0x0000, 0x0000, 0x0000 } }, + { 0x0472, { 0x0473, 0x0000, 0x0000, 0x0000 } }, + { 0x0474, { 0x0475, 0x0000, 0x0000, 0x0000 } }, + { 0x0476, { 0x0477, 0x0000, 0x0000, 0x0000 } }, + { 0x0478, { 0x0479, 0x0000, 0x0000, 0x0000 } }, + { 0x047A, { 0x047B, 0x0000, 0x0000, 0x0000 } }, + { 0x047C, { 0x047D, 0x0000, 0x0000, 0x0000 } }, + { 0x047E, { 0x047F, 0x0000, 0x0000, 0x0000 } }, + { 0x0480, { 0x0481, 0x0000, 0x0000, 0x0000 } }, + { 0x048A, { 0x048B, 0x0000, 0x0000, 0x0000 } }, + { 0x048C, { 0x048D, 0x0000, 0x0000, 0x0000 } }, + { 0x048E, { 0x048F, 0x0000, 0x0000, 0x0000 } }, + { 0x0490, { 0x0491, 0x0000, 0x0000, 0x0000 } }, + { 0x0492, { 0x0493, 0x0000, 0x0000, 0x0000 } }, + { 0x0494, { 0x0495, 0x0000, 0x0000, 0x0000 } }, + { 0x0496, { 0x0497, 0x0000, 0x0000, 0x0000 } }, + { 0x0498, { 0x0499, 0x0000, 0x0000, 0x0000 } }, + { 0x049A, { 0x049B, 0x0000, 0x0000, 0x0000 } }, + { 0x049C, { 0x049D, 0x0000, 0x0000, 0x0000 } }, + { 0x049E, { 0x049F, 0x0000, 0x0000, 0x0000 } }, + { 0x04A0, { 0x04A1, 0x0000, 0x0000, 0x0000 } }, + { 0x04A2, { 0x04A3, 0x0000, 0x0000, 0x0000 } }, + { 0x04A4, { 0x04A5, 0x0000, 0x0000, 0x0000 } }, + { 0x04A6, { 0x04A7, 0x0000, 0x0000, 0x0000 } }, + { 0x04A8, { 0x04A9, 0x0000, 0x0000, 0x0000 } }, + { 0x04AA, { 0x04AB, 0x0000, 0x0000, 0x0000 } }, + { 0x04AC, { 0x04AD, 0x0000, 0x0000, 0x0000 } }, + { 0x04AE, { 0x04AF, 0x0000, 0x0000, 0x0000 } }, + { 0x04B0, { 0x04B1, 0x0000, 0x0000, 0x0000 } }, + { 0x04B2, { 0x04B3, 0x0000, 0x0000, 0x0000 } }, + { 0x04B4, { 0x04B5, 0x0000, 0x0000, 0x0000 } }, + { 0x04B6, { 0x04B7, 0x0000, 0x0000, 0x0000 } }, + { 0x04B8, { 0x04B9, 0x0000, 0x0000, 0x0000 } }, + { 0x04BA, { 0x04BB, 0x0000, 0x0000, 0x0000 } }, + { 0x04BC, { 0x04BD, 0x0000, 0x0000, 0x0000 } }, + { 0x04BE, { 0x04BF, 0x0000, 0x0000, 0x0000 } }, + { 0x04C1, { 0x04C2, 0x0000, 0x0000, 0x0000 } }, + { 0x04C3, { 0x04C4, 0x0000, 0x0000, 0x0000 } }, + { 0x04C5, { 0x04C6, 0x0000, 0x0000, 0x0000 } }, + { 0x04C7, { 0x04C8, 0x0000, 0x0000, 0x0000 } }, + { 0x04C9, { 0x04CA, 0x0000, 0x0000, 0x0000 } }, + { 0x04CB, { 0x04CC, 0x0000, 0x0000, 0x0000 } }, + { 0x04CD, { 0x04CE, 0x0000, 0x0000, 0x0000 } }, + { 0x04D0, { 0x04D1, 0x0000, 0x0000, 0x0000 } }, + { 0x04D2, { 0x04D3, 0x0000, 0x0000, 0x0000 } }, + { 0x04D4, { 0x04D5, 0x0000, 0x0000, 0x0000 } }, + { 0x04D6, { 0x04D7, 0x0000, 0x0000, 0x0000 } }, + { 0x04D8, { 0x04D9, 0x0000, 0x0000, 0x0000 } }, + { 0x04DA, { 0x04DB, 0x0000, 0x0000, 0x0000 } }, + { 0x04DC, { 0x04DD, 0x0000, 0x0000, 0x0000 } }, + { 0x04DE, { 0x04DF, 0x0000, 0x0000, 0x0000 } }, + { 0x04E0, { 0x04E1, 0x0000, 0x0000, 0x0000 } }, + { 0x04E2, { 0x04E3, 0x0000, 0x0000, 0x0000 } }, + { 0x04E4, { 0x04E5, 0x0000, 0x0000, 0x0000 } }, + { 0x04E6, { 0x04E7, 0x0000, 0x0000, 0x0000 } }, + { 0x04E8, { 0x04E9, 0x0000, 0x0000, 0x0000 } }, + { 0x04EA, { 0x04EB, 0x0000, 0x0000, 0x0000 } }, + { 0x04EC, { 0x04ED, 0x0000, 0x0000, 0x0000 } }, + { 0x04EE, { 0x04EF, 0x0000, 0x0000, 0x0000 } }, + { 0x04F0, { 0x04F1, 0x0000, 0x0000, 0x0000 } }, + { 0x04F2, { 0x04F3, 0x0000, 0x0000, 0x0000 } }, + { 0x04F4, { 0x04F5, 0x0000, 0x0000, 0x0000 } }, + { 0x04F8, { 0x04F9, 0x0000, 0x0000, 0x0000 } }, + { 0x0500, { 0x0501, 0x0000, 0x0000, 0x0000 } }, + { 0x0502, { 0x0503, 0x0000, 0x0000, 0x0000 } }, + { 0x0504, { 0x0505, 0x0000, 0x0000, 0x0000 } }, + { 0x0506, { 0x0507, 0x0000, 0x0000, 0x0000 } }, + { 0x0508, { 0x0509, 0x0000, 0x0000, 0x0000 } }, + { 0x050A, { 0x050B, 0x0000, 0x0000, 0x0000 } }, + { 0x050C, { 0x050D, 0x0000, 0x0000, 0x0000 } }, + { 0x050E, { 0x050F, 0x0000, 0x0000, 0x0000 } }, + { 0x0531, { 0x0561, 0x0000, 0x0000, 0x0000 } }, + { 0x0532, { 0x0562, 0x0000, 0x0000, 0x0000 } }, + { 0x0533, { 0x0563, 0x0000, 0x0000, 0x0000 } }, + { 0x0534, { 0x0564, 0x0000, 0x0000, 0x0000 } }, + { 0x0535, { 0x0565, 0x0000, 0x0000, 0x0000 } }, + { 0x0536, { 0x0566, 0x0000, 0x0000, 0x0000 } }, + { 0x0537, { 0x0567, 0x0000, 0x0000, 0x0000 } }, + { 0x0538, { 0x0568, 0x0000, 0x0000, 0x0000 } }, + { 0x0539, { 0x0569, 0x0000, 0x0000, 0x0000 } }, + { 0x053A, { 0x056A, 0x0000, 0x0000, 0x0000 } }, + { 0x053B, { 0x056B, 0x0000, 0x0000, 0x0000 } }, + { 0x053C, { 0x056C, 0x0000, 0x0000, 0x0000 } }, + { 0x053D, { 0x056D, 0x0000, 0x0000, 0x0000 } }, + { 0x053E, { 0x056E, 0x0000, 0x0000, 0x0000 } }, + { 0x053F, { 0x056F, 0x0000, 0x0000, 0x0000 } }, + { 0x0540, { 0x0570, 0x0000, 0x0000, 0x0000 } }, + { 0x0541, { 0x0571, 0x0000, 0x0000, 0x0000 } }, + { 0x0542, { 0x0572, 0x0000, 0x0000, 0x0000 } }, + { 0x0543, { 0x0573, 0x0000, 0x0000, 0x0000 } }, + { 0x0544, { 0x0574, 0x0000, 0x0000, 0x0000 } }, + { 0x0545, { 0x0575, 0x0000, 0x0000, 0x0000 } }, + { 0x0546, { 0x0576, 0x0000, 0x0000, 0x0000 } }, + { 0x0547, { 0x0577, 0x0000, 0x0000, 0x0000 } }, + { 0x0548, { 0x0578, 0x0000, 0x0000, 0x0000 } }, + { 0x0549, { 0x0579, 0x0000, 0x0000, 0x0000 } }, + { 0x054A, { 0x057A, 0x0000, 0x0000, 0x0000 } }, + { 0x054B, { 0x057B, 0x0000, 0x0000, 0x0000 } }, + { 0x054C, { 0x057C, 0x0000, 0x0000, 0x0000 } }, + { 0x054D, { 0x057D, 0x0000, 0x0000, 0x0000 } }, + { 0x054E, { 0x057E, 0x0000, 0x0000, 0x0000 } }, + { 0x054F, { 0x057F, 0x0000, 0x0000, 0x0000 } }, + { 0x0550, { 0x0580, 0x0000, 0x0000, 0x0000 } }, + { 0x0551, { 0x0581, 0x0000, 0x0000, 0x0000 } }, + { 0x0552, { 0x0582, 0x0000, 0x0000, 0x0000 } }, + { 0x0553, { 0x0583, 0x0000, 0x0000, 0x0000 } }, + { 0x0554, { 0x0584, 0x0000, 0x0000, 0x0000 } }, + { 0x0555, { 0x0585, 0x0000, 0x0000, 0x0000 } }, + { 0x0556, { 0x0586, 0x0000, 0x0000, 0x0000 } }, + { 0x0587, { 0x0565, 0x0582, 0x0000, 0x0000 } }, + { 0x1E00, { 0x1E01, 0x0000, 0x0000, 0x0000 } }, + { 0x1E02, { 0x1E03, 0x0000, 0x0000, 0x0000 } }, + { 0x1E04, { 0x1E05, 0x0000, 0x0000, 0x0000 } }, + { 0x1E06, { 0x1E07, 0x0000, 0x0000, 0x0000 } }, + { 0x1E08, { 0x1E09, 0x0000, 0x0000, 0x0000 } }, + { 0x1E0A, { 0x1E0B, 0x0000, 0x0000, 0x0000 } }, + { 0x1E0C, { 0x1E0D, 0x0000, 0x0000, 0x0000 } }, + { 0x1E0E, { 0x1E0F, 0x0000, 0x0000, 0x0000 } }, + { 0x1E10, { 0x1E11, 0x0000, 0x0000, 0x0000 } }, + { 0x1E12, { 0x1E13, 0x0000, 0x0000, 0x0000 } }, + { 0x1E14, { 0x1E15, 0x0000, 0x0000, 0x0000 } }, + { 0x1E16, { 0x1E17, 0x0000, 0x0000, 0x0000 } }, + { 0x1E18, { 0x1E19, 0x0000, 0x0000, 0x0000 } }, + { 0x1E1A, { 0x1E1B, 0x0000, 0x0000, 0x0000 } }, + { 0x1E1C, { 0x1E1D, 0x0000, 0x0000, 0x0000 } }, + { 0x1E1E, { 0x1E1F, 0x0000, 0x0000, 0x0000 } }, + { 0x1E20, { 0x1E21, 0x0000, 0x0000, 0x0000 } }, + { 0x1E22, { 0x1E23, 0x0000, 0x0000, 0x0000 } }, + { 0x1E24, { 0x1E25, 0x0000, 0x0000, 0x0000 } }, + { 0x1E26, { 0x1E27, 0x0000, 0x0000, 0x0000 } }, + { 0x1E28, { 0x1E29, 0x0000, 0x0000, 0x0000 } }, + { 0x1E2A, { 0x1E2B, 0x0000, 0x0000, 0x0000 } }, + { 0x1E2C, { 0x1E2D, 0x0000, 0x0000, 0x0000 } }, + { 0x1E2E, { 0x1E2F, 0x0000, 0x0000, 0x0000 } }, + { 0x1E30, { 0x1E31, 0x0000, 0x0000, 0x0000 } }, + { 0x1E32, { 0x1E33, 0x0000, 0x0000, 0x0000 } }, + { 0x1E34, { 0x1E35, 0x0000, 0x0000, 0x0000 } }, + { 0x1E36, { 0x1E37, 0x0000, 0x0000, 0x0000 } }, + { 0x1E38, { 0x1E39, 0x0000, 0x0000, 0x0000 } }, + { 0x1E3A, { 0x1E3B, 0x0000, 0x0000, 0x0000 } }, + { 0x1E3C, { 0x1E3D, 0x0000, 0x0000, 0x0000 } }, + { 0x1E3E, { 0x1E3F, 0x0000, 0x0000, 0x0000 } }, + { 0x1E40, { 0x1E41, 0x0000, 0x0000, 0x0000 } }, + { 0x1E42, { 0x1E43, 0x0000, 0x0000, 0x0000 } }, + { 0x1E44, { 0x1E45, 0x0000, 0x0000, 0x0000 } }, + { 0x1E46, { 0x1E47, 0x0000, 0x0000, 0x0000 } }, + { 0x1E48, { 0x1E49, 0x0000, 0x0000, 0x0000 } }, + { 0x1E4A, { 0x1E4B, 0x0000, 0x0000, 0x0000 } }, + { 0x1E4C, { 0x1E4D, 0x0000, 0x0000, 0x0000 } }, + { 0x1E4E, { 0x1E4F, 0x0000, 0x0000, 0x0000 } }, + { 0x1E50, { 0x1E51, 0x0000, 0x0000, 0x0000 } }, + { 0x1E52, { 0x1E53, 0x0000, 0x0000, 0x0000 } }, + { 0x1E54, { 0x1E55, 0x0000, 0x0000, 0x0000 } }, + { 0x1E56, { 0x1E57, 0x0000, 0x0000, 0x0000 } }, + { 0x1E58, { 0x1E59, 0x0000, 0x0000, 0x0000 } }, + { 0x1E5A, { 0x1E5B, 0x0000, 0x0000, 0x0000 } }, + { 0x1E5C, { 0x1E5D, 0x0000, 0x0000, 0x0000 } }, + { 0x1E5E, { 0x1E5F, 0x0000, 0x0000, 0x0000 } }, + { 0x1E60, { 0x1E61, 0x0000, 0x0000, 0x0000 } }, + { 0x1E62, { 0x1E63, 0x0000, 0x0000, 0x0000 } }, + { 0x1E64, { 0x1E65, 0x0000, 0x0000, 0x0000 } }, + { 0x1E66, { 0x1E67, 0x0000, 0x0000, 0x0000 } }, + { 0x1E68, { 0x1E69, 0x0000, 0x0000, 0x0000 } }, + { 0x1E6A, { 0x1E6B, 0x0000, 0x0000, 0x0000 } }, + { 0x1E6C, { 0x1E6D, 0x0000, 0x0000, 0x0000 } }, + { 0x1E6E, { 0x1E6F, 0x0000, 0x0000, 0x0000 } }, + { 0x1E70, { 0x1E71, 0x0000, 0x0000, 0x0000 } }, + { 0x1E72, { 0x1E73, 0x0000, 0x0000, 0x0000 } }, + { 0x1E74, { 0x1E75, 0x0000, 0x0000, 0x0000 } }, + { 0x1E76, { 0x1E77, 0x0000, 0x0000, 0x0000 } }, + { 0x1E78, { 0x1E79, 0x0000, 0x0000, 0x0000 } }, + { 0x1E7A, { 0x1E7B, 0x0000, 0x0000, 0x0000 } }, + { 0x1E7C, { 0x1E7D, 0x0000, 0x0000, 0x0000 } }, + { 0x1E7E, { 0x1E7F, 0x0000, 0x0000, 0x0000 } }, + { 0x1E80, { 0x1E81, 0x0000, 0x0000, 0x0000 } }, + { 0x1E82, { 0x1E83, 0x0000, 0x0000, 0x0000 } }, + { 0x1E84, { 0x1E85, 0x0000, 0x0000, 0x0000 } }, + { 0x1E86, { 0x1E87, 0x0000, 0x0000, 0x0000 } }, + { 0x1E88, { 0x1E89, 0x0000, 0x0000, 0x0000 } }, + { 0x1E8A, { 0x1E8B, 0x0000, 0x0000, 0x0000 } }, + { 0x1E8C, { 0x1E8D, 0x0000, 0x0000, 0x0000 } }, + { 0x1E8E, { 0x1E8F, 0x0000, 0x0000, 0x0000 } }, + { 0x1E90, { 0x1E91, 0x0000, 0x0000, 0x0000 } }, + { 0x1E92, { 0x1E93, 0x0000, 0x0000, 0x0000 } }, + { 0x1E94, { 0x1E95, 0x0000, 0x0000, 0x0000 } }, + { 0x1E96, { 0x0068, 0x0331, 0x0000, 0x0000 } }, + { 0x1E97, { 0x0074, 0x0308, 0x0000, 0x0000 } }, + { 0x1E98, { 0x0077, 0x030A, 0x0000, 0x0000 } }, + { 0x1E99, { 0x0079, 0x030A, 0x0000, 0x0000 } }, + { 0x1E9A, { 0x0061, 0x02BE, 0x0000, 0x0000 } }, + { 0x1E9B, { 0x1E61, 0x0000, 0x0000, 0x0000 } }, + { 0x1EA0, { 0x1EA1, 0x0000, 0x0000, 0x0000 } }, + { 0x1EA2, { 0x1EA3, 0x0000, 0x0000, 0x0000 } }, + { 0x1EA4, { 0x1EA5, 0x0000, 0x0000, 0x0000 } }, + { 0x1EA6, { 0x1EA7, 0x0000, 0x0000, 0x0000 } }, + { 0x1EA8, { 0x1EA9, 0x0000, 0x0000, 0x0000 } }, + { 0x1EAA, { 0x1EAB, 0x0000, 0x0000, 0x0000 } }, + { 0x1EAC, { 0x1EAD, 0x0000, 0x0000, 0x0000 } }, + { 0x1EAE, { 0x1EAF, 0x0000, 0x0000, 0x0000 } }, + { 0x1EB0, { 0x1EB1, 0x0000, 0x0000, 0x0000 } }, + { 0x1EB2, { 0x1EB3, 0x0000, 0x0000, 0x0000 } }, + { 0x1EB4, { 0x1EB5, 0x0000, 0x0000, 0x0000 } }, + { 0x1EB6, { 0x1EB7, 0x0000, 0x0000, 0x0000 } }, + { 0x1EB8, { 0x1EB9, 0x0000, 0x0000, 0x0000 } }, + { 0x1EBA, { 0x1EBB, 0x0000, 0x0000, 0x0000 } }, + { 0x1EBC, { 0x1EBD, 0x0000, 0x0000, 0x0000 } }, + { 0x1EBE, { 0x1EBF, 0x0000, 0x0000, 0x0000 } }, + { 0x1EC0, { 0x1EC1, 0x0000, 0x0000, 0x0000 } }, + { 0x1EC2, { 0x1EC3, 0x0000, 0x0000, 0x0000 } }, + { 0x1EC4, { 0x1EC5, 0x0000, 0x0000, 0x0000 } }, + { 0x1EC6, { 0x1EC7, 0x0000, 0x0000, 0x0000 } }, + { 0x1EC8, { 0x1EC9, 0x0000, 0x0000, 0x0000 } }, + { 0x1ECA, { 0x1ECB, 0x0000, 0x0000, 0x0000 } }, + { 0x1ECC, { 0x1ECD, 0x0000, 0x0000, 0x0000 } }, + { 0x1ECE, { 0x1ECF, 0x0000, 0x0000, 0x0000 } }, + { 0x1ED0, { 0x1ED1, 0x0000, 0x0000, 0x0000 } }, + { 0x1ED2, { 0x1ED3, 0x0000, 0x0000, 0x0000 } }, + { 0x1ED4, { 0x1ED5, 0x0000, 0x0000, 0x0000 } }, + { 0x1ED6, { 0x1ED7, 0x0000, 0x0000, 0x0000 } }, + { 0x1ED8, { 0x1ED9, 0x0000, 0x0000, 0x0000 } }, + { 0x1EDA, { 0x1EDB, 0x0000, 0x0000, 0x0000 } }, + { 0x1EDC, { 0x1EDD, 0x0000, 0x0000, 0x0000 } }, + { 0x1EDE, { 0x1EDF, 0x0000, 0x0000, 0x0000 } }, + { 0x1EE0, { 0x1EE1, 0x0000, 0x0000, 0x0000 } }, + { 0x1EE2, { 0x1EE3, 0x0000, 0x0000, 0x0000 } }, + { 0x1EE4, { 0x1EE5, 0x0000, 0x0000, 0x0000 } }, + { 0x1EE6, { 0x1EE7, 0x0000, 0x0000, 0x0000 } }, + { 0x1EE8, { 0x1EE9, 0x0000, 0x0000, 0x0000 } }, + { 0x1EEA, { 0x1EEB, 0x0000, 0x0000, 0x0000 } }, + { 0x1EEC, { 0x1EED, 0x0000, 0x0000, 0x0000 } }, + { 0x1EEE, { 0x1EEF, 0x0000, 0x0000, 0x0000 } }, + { 0x1EF0, { 0x1EF1, 0x0000, 0x0000, 0x0000 } }, + { 0x1EF2, { 0x1EF3, 0x0000, 0x0000, 0x0000 } }, + { 0x1EF4, { 0x1EF5, 0x0000, 0x0000, 0x0000 } }, + { 0x1EF6, { 0x1EF7, 0x0000, 0x0000, 0x0000 } }, + { 0x1EF8, { 0x1EF9, 0x0000, 0x0000, 0x0000 } }, + { 0x1F08, { 0x1F00, 0x0000, 0x0000, 0x0000 } }, + { 0x1F09, { 0x1F01, 0x0000, 0x0000, 0x0000 } }, + { 0x1F0A, { 0x1F02, 0x0000, 0x0000, 0x0000 } }, + { 0x1F0B, { 0x1F03, 0x0000, 0x0000, 0x0000 } }, + { 0x1F0C, { 0x1F04, 0x0000, 0x0000, 0x0000 } }, + { 0x1F0D, { 0x1F05, 0x0000, 0x0000, 0x0000 } }, + { 0x1F0E, { 0x1F06, 0x0000, 0x0000, 0x0000 } }, + { 0x1F0F, { 0x1F07, 0x0000, 0x0000, 0x0000 } }, + { 0x1F18, { 0x1F10, 0x0000, 0x0000, 0x0000 } }, + { 0x1F19, { 0x1F11, 0x0000, 0x0000, 0x0000 } }, + { 0x1F1A, { 0x1F12, 0x0000, 0x0000, 0x0000 } }, + { 0x1F1B, { 0x1F13, 0x0000, 0x0000, 0x0000 } }, + { 0x1F1C, { 0x1F14, 0x0000, 0x0000, 0x0000 } }, + { 0x1F1D, { 0x1F15, 0x0000, 0x0000, 0x0000 } }, + { 0x1F28, { 0x1F20, 0x0000, 0x0000, 0x0000 } }, + { 0x1F29, { 0x1F21, 0x0000, 0x0000, 0x0000 } }, + { 0x1F2A, { 0x1F22, 0x0000, 0x0000, 0x0000 } }, + { 0x1F2B, { 0x1F23, 0x0000, 0x0000, 0x0000 } }, + { 0x1F2C, { 0x1F24, 0x0000, 0x0000, 0x0000 } }, + { 0x1F2D, { 0x1F25, 0x0000, 0x0000, 0x0000 } }, + { 0x1F2E, { 0x1F26, 0x0000, 0x0000, 0x0000 } }, + { 0x1F2F, { 0x1F27, 0x0000, 0x0000, 0x0000 } }, + { 0x1F38, { 0x1F30, 0x0000, 0x0000, 0x0000 } }, + { 0x1F39, { 0x1F31, 0x0000, 0x0000, 0x0000 } }, + { 0x1F3A, { 0x1F32, 0x0000, 0x0000, 0x0000 } }, + { 0x1F3B, { 0x1F33, 0x0000, 0x0000, 0x0000 } }, + { 0x1F3C, { 0x1F34, 0x0000, 0x0000, 0x0000 } }, + { 0x1F3D, { 0x1F35, 0x0000, 0x0000, 0x0000 } }, + { 0x1F3E, { 0x1F36, 0x0000, 0x0000, 0x0000 } }, + { 0x1F3F, { 0x1F37, 0x0000, 0x0000, 0x0000 } }, + { 0x1F48, { 0x1F40, 0x0000, 0x0000, 0x0000 } }, + { 0x1F49, { 0x1F41, 0x0000, 0x0000, 0x0000 } }, + { 0x1F4A, { 0x1F42, 0x0000, 0x0000, 0x0000 } }, + { 0x1F4B, { 0x1F43, 0x0000, 0x0000, 0x0000 } }, + { 0x1F4C, { 0x1F44, 0x0000, 0x0000, 0x0000 } }, + { 0x1F4D, { 0x1F45, 0x0000, 0x0000, 0x0000 } }, + { 0x1F50, { 0x03C5, 0x0313, 0x0000, 0x0000 } }, + { 0x1F52, { 0x03C5, 0x0313, 0x0300, 0x0000 } }, + { 0x1F54, { 0x03C5, 0x0313, 0x0301, 0x0000 } }, + { 0x1F56, { 0x03C5, 0x0313, 0x0342, 0x0000 } }, + { 0x1F59, { 0x1F51, 0x0000, 0x0000, 0x0000 } }, + { 0x1F5B, { 0x1F53, 0x0000, 0x0000, 0x0000 } }, + { 0x1F5D, { 0x1F55, 0x0000, 0x0000, 0x0000 } }, + { 0x1F5F, { 0x1F57, 0x0000, 0x0000, 0x0000 } }, + { 0x1F68, { 0x1F60, 0x0000, 0x0000, 0x0000 } }, + { 0x1F69, { 0x1F61, 0x0000, 0x0000, 0x0000 } }, + { 0x1F6A, { 0x1F62, 0x0000, 0x0000, 0x0000 } }, + { 0x1F6B, { 0x1F63, 0x0000, 0x0000, 0x0000 } }, + { 0x1F6C, { 0x1F64, 0x0000, 0x0000, 0x0000 } }, + { 0x1F6D, { 0x1F65, 0x0000, 0x0000, 0x0000 } }, + { 0x1F6E, { 0x1F66, 0x0000, 0x0000, 0x0000 } }, + { 0x1F6F, { 0x1F67, 0x0000, 0x0000, 0x0000 } }, + { 0x1F80, { 0x1F00, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F81, { 0x1F01, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F82, { 0x1F02, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F83, { 0x1F03, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F84, { 0x1F04, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F85, { 0x1F05, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F86, { 0x1F06, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F87, { 0x1F07, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F88, { 0x1F00, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F89, { 0x1F01, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F8A, { 0x1F02, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F8B, { 0x1F03, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F8C, { 0x1F04, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F8D, { 0x1F05, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F8E, { 0x1F06, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F8F, { 0x1F07, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F90, { 0x1F20, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F91, { 0x1F21, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F92, { 0x1F22, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F93, { 0x1F23, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F94, { 0x1F24, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F95, { 0x1F25, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F96, { 0x1F26, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F97, { 0x1F27, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F98, { 0x1F20, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F99, { 0x1F21, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F9A, { 0x1F22, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F9B, { 0x1F23, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F9C, { 0x1F24, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F9D, { 0x1F25, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F9E, { 0x1F26, 0x03B9, 0x0000, 0x0000 } }, + { 0x1F9F, { 0x1F27, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FA0, { 0x1F60, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FA1, { 0x1F61, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FA2, { 0x1F62, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FA3, { 0x1F63, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FA4, { 0x1F64, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FA5, { 0x1F65, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FA6, { 0x1F66, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FA7, { 0x1F67, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FA8, { 0x1F60, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FA9, { 0x1F61, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FAA, { 0x1F62, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FAB, { 0x1F63, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FAC, { 0x1F64, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FAD, { 0x1F65, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FAE, { 0x1F66, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FAF, { 0x1F67, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FB2, { 0x1F70, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FB3, { 0x03B1, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FB4, { 0x03AC, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FB6, { 0x03B1, 0x0342, 0x0000, 0x0000 } }, + { 0x1FB7, { 0x03B1, 0x0342, 0x03B9, 0x0000 } }, + { 0x1FB8, { 0x1FB0, 0x0000, 0x0000, 0x0000 } }, + { 0x1FB9, { 0x1FB1, 0x0000, 0x0000, 0x0000 } }, + { 0x1FBA, { 0x1F70, 0x0000, 0x0000, 0x0000 } }, + { 0x1FBB, { 0x1F71, 0x0000, 0x0000, 0x0000 } }, + { 0x1FBC, { 0x03B1, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FBE, { 0x03B9, 0x0000, 0x0000, 0x0000 } }, + { 0x1FC2, { 0x1F74, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FC3, { 0x03B7, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FC4, { 0x03AE, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FC6, { 0x03B7, 0x0342, 0x0000, 0x0000 } }, + { 0x1FC7, { 0x03B7, 0x0342, 0x03B9, 0x0000 } }, + { 0x1FC8, { 0x1F72, 0x0000, 0x0000, 0x0000 } }, + { 0x1FC9, { 0x1F73, 0x0000, 0x0000, 0x0000 } }, + { 0x1FCA, { 0x1F74, 0x0000, 0x0000, 0x0000 } }, + { 0x1FCB, { 0x1F75, 0x0000, 0x0000, 0x0000 } }, + { 0x1FCC, { 0x03B7, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FD2, { 0x03B9, 0x0308, 0x0300, 0x0000 } }, + { 0x1FD3, { 0x03B9, 0x0308, 0x0301, 0x0000 } }, + { 0x1FD6, { 0x03B9, 0x0342, 0x0000, 0x0000 } }, + { 0x1FD7, { 0x03B9, 0x0308, 0x0342, 0x0000 } }, + { 0x1FD8, { 0x1FD0, 0x0000, 0x0000, 0x0000 } }, + { 0x1FD9, { 0x1FD1, 0x0000, 0x0000, 0x0000 } }, + { 0x1FDA, { 0x1F76, 0x0000, 0x0000, 0x0000 } }, + { 0x1FDB, { 0x1F77, 0x0000, 0x0000, 0x0000 } }, + { 0x1FE2, { 0x03C5, 0x0308, 0x0300, 0x0000 } }, + { 0x1FE3, { 0x03C5, 0x0308, 0x0301, 0x0000 } }, + { 0x1FE4, { 0x03C1, 0x0313, 0x0000, 0x0000 } }, + { 0x1FE6, { 0x03C5, 0x0342, 0x0000, 0x0000 } }, + { 0x1FE7, { 0x03C5, 0x0308, 0x0342, 0x0000 } }, + { 0x1FE8, { 0x1FE0, 0x0000, 0x0000, 0x0000 } }, + { 0x1FE9, { 0x1FE1, 0x0000, 0x0000, 0x0000 } }, + { 0x1FEA, { 0x1F7A, 0x0000, 0x0000, 0x0000 } }, + { 0x1FEB, { 0x1F7B, 0x0000, 0x0000, 0x0000 } }, + { 0x1FEC, { 0x1FE5, 0x0000, 0x0000, 0x0000 } }, + { 0x1FF2, { 0x1F7C, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FF3, { 0x03C9, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FF4, { 0x03CE, 0x03B9, 0x0000, 0x0000 } }, + { 0x1FF6, { 0x03C9, 0x0342, 0x0000, 0x0000 } }, + { 0x1FF7, { 0x03C9, 0x0342, 0x03B9, 0x0000 } }, + { 0x1FF8, { 0x1F78, 0x0000, 0x0000, 0x0000 } }, + { 0x1FF9, { 0x1F79, 0x0000, 0x0000, 0x0000 } }, + { 0x1FFA, { 0x1F7C, 0x0000, 0x0000, 0x0000 } }, + { 0x1FFB, { 0x1F7D, 0x0000, 0x0000, 0x0000 } }, + { 0x1FFC, { 0x03C9, 0x03B9, 0x0000, 0x0000 } }, + { 0x20A8, { 0x0072, 0x0073, 0x0000, 0x0000 } }, + { 0x2102, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x2103, { 0x00B0, 0x0063, 0x0000, 0x0000 } }, + { 0x2107, { 0x025B, 0x0000, 0x0000, 0x0000 } }, + { 0x2109, { 0x00B0, 0x0066, 0x0000, 0x0000 } }, + { 0x210B, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x210C, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x210D, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x2110, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x2111, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x2112, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x2115, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x2116, { 0x006E, 0x006F, 0x0000, 0x0000 } }, + { 0x2119, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x211A, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x211B, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x211C, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x211D, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x2120, { 0x0073, 0x006D, 0x0000, 0x0000 } }, + { 0x2121, { 0x0074, 0x0065, 0x006C, 0x0000 } }, + { 0x2122, { 0x0074, 0x006D, 0x0000, 0x0000 } }, + { 0x2124, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x2126, { 0x03C9, 0x0000, 0x0000, 0x0000 } }, + { 0x2128, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x212A, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x212B, { 0x00E5, 0x0000, 0x0000, 0x0000 } }, + { 0x212C, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x212D, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x2130, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x2131, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x2133, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x213E, { 0x03B3, 0x0000, 0x0000, 0x0000 } }, + { 0x213F, { 0x03C0, 0x0000, 0x0000, 0x0000 } }, + { 0x2145, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x2160, { 0x2170, 0x0000, 0x0000, 0x0000 } }, + { 0x2161, { 0x2171, 0x0000, 0x0000, 0x0000 } }, + { 0x2162, { 0x2172, 0x0000, 0x0000, 0x0000 } }, + { 0x2163, { 0x2173, 0x0000, 0x0000, 0x0000 } }, + { 0x2164, { 0x2174, 0x0000, 0x0000, 0x0000 } }, + { 0x2165, { 0x2175, 0x0000, 0x0000, 0x0000 } }, + { 0x2166, { 0x2176, 0x0000, 0x0000, 0x0000 } }, + { 0x2167, { 0x2177, 0x0000, 0x0000, 0x0000 } }, + { 0x2168, { 0x2178, 0x0000, 0x0000, 0x0000 } }, + { 0x2169, { 0x2179, 0x0000, 0x0000, 0x0000 } }, + { 0x216A, { 0x217A, 0x0000, 0x0000, 0x0000 } }, + { 0x216B, { 0x217B, 0x0000, 0x0000, 0x0000 } }, + { 0x216C, { 0x217C, 0x0000, 0x0000, 0x0000 } }, + { 0x216D, { 0x217D, 0x0000, 0x0000, 0x0000 } }, + { 0x216E, { 0x217E, 0x0000, 0x0000, 0x0000 } }, + { 0x216F, { 0x217F, 0x0000, 0x0000, 0x0000 } }, + { 0x24B6, { 0x24D0, 0x0000, 0x0000, 0x0000 } }, + { 0x24B7, { 0x24D1, 0x0000, 0x0000, 0x0000 } }, + { 0x24B8, { 0x24D2, 0x0000, 0x0000, 0x0000 } }, + { 0x24B9, { 0x24D3, 0x0000, 0x0000, 0x0000 } }, + { 0x24BA, { 0x24D4, 0x0000, 0x0000, 0x0000 } }, + { 0x24BB, { 0x24D5, 0x0000, 0x0000, 0x0000 } }, + { 0x24BC, { 0x24D6, 0x0000, 0x0000, 0x0000 } }, + { 0x24BD, { 0x24D7, 0x0000, 0x0000, 0x0000 } }, + { 0x24BE, { 0x24D8, 0x0000, 0x0000, 0x0000 } }, + { 0x24BF, { 0x24D9, 0x0000, 0x0000, 0x0000 } }, + { 0x24C0, { 0x24DA, 0x0000, 0x0000, 0x0000 } }, + { 0x24C1, { 0x24DB, 0x0000, 0x0000, 0x0000 } }, + { 0x24C2, { 0x24DC, 0x0000, 0x0000, 0x0000 } }, + { 0x24C3, { 0x24DD, 0x0000, 0x0000, 0x0000 } }, + { 0x24C4, { 0x24DE, 0x0000, 0x0000, 0x0000 } }, + { 0x24C5, { 0x24DF, 0x0000, 0x0000, 0x0000 } }, + { 0x24C6, { 0x24E0, 0x0000, 0x0000, 0x0000 } }, + { 0x24C7, { 0x24E1, 0x0000, 0x0000, 0x0000 } }, + { 0x24C8, { 0x24E2, 0x0000, 0x0000, 0x0000 } }, + { 0x24C9, { 0x24E3, 0x0000, 0x0000, 0x0000 } }, + { 0x24CA, { 0x24E4, 0x0000, 0x0000, 0x0000 } }, + { 0x24CB, { 0x24E5, 0x0000, 0x0000, 0x0000 } }, + { 0x24CC, { 0x24E6, 0x0000, 0x0000, 0x0000 } }, + { 0x24CD, { 0x24E7, 0x0000, 0x0000, 0x0000 } }, + { 0x24CE, { 0x24E8, 0x0000, 0x0000, 0x0000 } }, + { 0x24CF, { 0x24E9, 0x0000, 0x0000, 0x0000 } }, + { 0x3371, { 0x0068, 0x0070, 0x0061, 0x0000 } }, + { 0x3373, { 0x0061, 0x0075, 0x0000, 0x0000 } }, + { 0x3375, { 0x006F, 0x0076, 0x0000, 0x0000 } }, + { 0x3380, { 0x0070, 0x0061, 0x0000, 0x0000 } }, + { 0x3381, { 0x006E, 0x0061, 0x0000, 0x0000 } }, + { 0x3382, { 0x03BC, 0x0061, 0x0000, 0x0000 } }, + { 0x3383, { 0x006D, 0x0061, 0x0000, 0x0000 } }, + { 0x3384, { 0x006B, 0x0061, 0x0000, 0x0000 } }, + { 0x3385, { 0x006B, 0x0062, 0x0000, 0x0000 } }, + { 0x3386, { 0x006D, 0x0062, 0x0000, 0x0000 } }, + { 0x3387, { 0x0067, 0x0062, 0x0000, 0x0000 } }, + { 0x338A, { 0x0070, 0x0066, 0x0000, 0x0000 } }, + { 0x338B, { 0x006E, 0x0066, 0x0000, 0x0000 } }, + { 0x338C, { 0x03BC, 0x0066, 0x0000, 0x0000 } }, + { 0x3390, { 0x0068, 0x007A, 0x0000, 0x0000 } }, + { 0x3391, { 0x006B, 0x0068, 0x007A, 0x0000 } }, + { 0x3392, { 0x006D, 0x0068, 0x007A, 0x0000 } }, + { 0x3393, { 0x0067, 0x0068, 0x007A, 0x0000 } }, + { 0x3394, { 0x0074, 0x0068, 0x007A, 0x0000 } }, + { 0x33A9, { 0x0070, 0x0061, 0x0000, 0x0000 } }, + { 0x33AA, { 0x006B, 0x0070, 0x0061, 0x0000 } }, + { 0x33AB, { 0x006D, 0x0070, 0x0061, 0x0000 } }, + { 0x33AC, { 0x0067, 0x0070, 0x0061, 0x0000 } }, + { 0x33B4, { 0x0070, 0x0076, 0x0000, 0x0000 } }, + { 0x33B5, { 0x006E, 0x0076, 0x0000, 0x0000 } }, + { 0x33B6, { 0x03BC, 0x0076, 0x0000, 0x0000 } }, + { 0x33B7, { 0x006D, 0x0076, 0x0000, 0x0000 } }, + { 0x33B8, { 0x006B, 0x0076, 0x0000, 0x0000 } }, + { 0x33B9, { 0x006D, 0x0076, 0x0000, 0x0000 } }, + { 0x33BA, { 0x0070, 0x0077, 0x0000, 0x0000 } }, + { 0x33BB, { 0x006E, 0x0077, 0x0000, 0x0000 } }, + { 0x33BC, { 0x03BC, 0x0077, 0x0000, 0x0000 } }, + { 0x33BD, { 0x006D, 0x0077, 0x0000, 0x0000 } }, + { 0x33BE, { 0x006B, 0x0077, 0x0000, 0x0000 } }, + { 0x33BF, { 0x006D, 0x0077, 0x0000, 0x0000 } }, + { 0x33C0, { 0x006B, 0x03C9, 0x0000, 0x0000 } }, + { 0x33C1, { 0x006D, 0x03C9, 0x0000, 0x0000 } }, + { 0x33C3, { 0x0062, 0x0071, 0x0000, 0x0000 } }, + { 0x33C6, { 0x0063, 0x2215, 0x006B, 0x0067 } }, + { 0x33C7, { 0x0063, 0x006F, 0x002E, 0x0000 } }, + { 0x33C8, { 0x0064, 0x0062, 0x0000, 0x0000 } }, + { 0x33C9, { 0x0067, 0x0079, 0x0000, 0x0000 } }, + { 0x33CB, { 0x0068, 0x0070, 0x0000, 0x0000 } }, + { 0x33CD, { 0x006B, 0x006B, 0x0000, 0x0000 } }, + { 0x33CE, { 0x006B, 0x006D, 0x0000, 0x0000 } }, + { 0x33D7, { 0x0070, 0x0068, 0x0000, 0x0000 } }, + { 0x33D9, { 0x0070, 0x0070, 0x006D, 0x0000 } }, + { 0x33DA, { 0x0070, 0x0072, 0x0000, 0x0000 } }, + { 0x33DC, { 0x0073, 0x0076, 0x0000, 0x0000 } }, + { 0x33DD, { 0x0077, 0x0062, 0x0000, 0x0000 } }, + { 0xFB00, { 0x0066, 0x0066, 0x0000, 0x0000 } }, + { 0xFB01, { 0x0066, 0x0069, 0x0000, 0x0000 } }, + { 0xFB02, { 0x0066, 0x006C, 0x0000, 0x0000 } }, + { 0xFB03, { 0x0066, 0x0066, 0x0069, 0x0000 } }, + { 0xFB04, { 0x0066, 0x0066, 0x006C, 0x0000 } }, + { 0xFB05, { 0x0073, 0x0074, 0x0000, 0x0000 } }, + { 0xFB06, { 0x0073, 0x0074, 0x0000, 0x0000 } }, + { 0xFB13, { 0x0574, 0x0576, 0x0000, 0x0000 } }, + { 0xFB14, { 0x0574, 0x0565, 0x0000, 0x0000 } }, + { 0xFB15, { 0x0574, 0x056B, 0x0000, 0x0000 } }, + { 0xFB16, { 0x057E, 0x0576, 0x0000, 0x0000 } }, + { 0xFB17, { 0x0574, 0x056D, 0x0000, 0x0000 } }, + { 0xFF21, { 0xFF41, 0x0000, 0x0000, 0x0000 } }, + { 0xFF22, { 0xFF42, 0x0000, 0x0000, 0x0000 } }, + { 0xFF23, { 0xFF43, 0x0000, 0x0000, 0x0000 } }, + { 0xFF24, { 0xFF44, 0x0000, 0x0000, 0x0000 } }, + { 0xFF25, { 0xFF45, 0x0000, 0x0000, 0x0000 } }, + { 0xFF26, { 0xFF46, 0x0000, 0x0000, 0x0000 } }, + { 0xFF27, { 0xFF47, 0x0000, 0x0000, 0x0000 } }, + { 0xFF28, { 0xFF48, 0x0000, 0x0000, 0x0000 } }, + { 0xFF29, { 0xFF49, 0x0000, 0x0000, 0x0000 } }, + { 0xFF2A, { 0xFF4A, 0x0000, 0x0000, 0x0000 } }, + { 0xFF2B, { 0xFF4B, 0x0000, 0x0000, 0x0000 } }, + { 0xFF2C, { 0xFF4C, 0x0000, 0x0000, 0x0000 } }, + { 0xFF2D, { 0xFF4D, 0x0000, 0x0000, 0x0000 } }, + { 0xFF2E, { 0xFF4E, 0x0000, 0x0000, 0x0000 } }, + { 0xFF2F, { 0xFF4F, 0x0000, 0x0000, 0x0000 } }, + { 0xFF30, { 0xFF50, 0x0000, 0x0000, 0x0000 } }, + { 0xFF31, { 0xFF51, 0x0000, 0x0000, 0x0000 } }, + { 0xFF32, { 0xFF52, 0x0000, 0x0000, 0x0000 } }, + { 0xFF33, { 0xFF53, 0x0000, 0x0000, 0x0000 } }, + { 0xFF34, { 0xFF54, 0x0000, 0x0000, 0x0000 } }, + { 0xFF35, { 0xFF55, 0x0000, 0x0000, 0x0000 } }, + { 0xFF36, { 0xFF56, 0x0000, 0x0000, 0x0000 } }, + { 0xFF37, { 0xFF57, 0x0000, 0x0000, 0x0000 } }, + { 0xFF38, { 0xFF58, 0x0000, 0x0000, 0x0000 } }, + { 0xFF39, { 0xFF59, 0x0000, 0x0000, 0x0000 } }, + { 0xFF3A, { 0xFF5A, 0x0000, 0x0000, 0x0000 } }, + { 0x10400, { 0xd801, 0xdc28, 0x0000, 0x0000 } }, + { 0x10401, { 0xd801, 0xdc29, 0x0000, 0x0000 } }, + { 0x10402, { 0xd801, 0xdc2A, 0x0000, 0x0000 } }, + { 0x10403, { 0xd801, 0xdc2B, 0x0000, 0x0000 } }, + { 0x10404, { 0xd801, 0xdc2C, 0x0000, 0x0000 } }, + { 0x10405, { 0xd801, 0xdc2D, 0x0000, 0x0000 } }, + { 0x10406, { 0xd801, 0xdc2E, 0x0000, 0x0000 } }, + { 0x10407, { 0xd801, 0xdc2F, 0x0000, 0x0000 } }, + { 0x10408, { 0xd801, 0xdc30, 0x0000, 0x0000 } }, + { 0x10409, { 0xd801, 0xdc31, 0x0000, 0x0000 } }, + { 0x1040A, { 0xd801, 0xdc32, 0x0000, 0x0000 } }, + { 0x1040B, { 0xd801, 0xdc33, 0x0000, 0x0000 } }, + { 0x1040C, { 0xd801, 0xdc34, 0x0000, 0x0000 } }, + { 0x1040D, { 0xd801, 0xdc35, 0x0000, 0x0000 } }, + { 0x1040E, { 0xd801, 0xdc36, 0x0000, 0x0000 } }, + { 0x1040F, { 0xd801, 0xdc37, 0x0000, 0x0000 } }, + { 0x10410, { 0xd801, 0xdc38, 0x0000, 0x0000 } }, + { 0x10411, { 0xd801, 0xdc39, 0x0000, 0x0000 } }, + { 0x10412, { 0xd801, 0xdc3A, 0x0000, 0x0000 } }, + { 0x10413, { 0xd801, 0xdc3B, 0x0000, 0x0000 } }, + { 0x10414, { 0xd801, 0xdc3C, 0x0000, 0x0000 } }, + { 0x10415, { 0xd801, 0xdc3D, 0x0000, 0x0000 } }, + { 0x10416, { 0xd801, 0xdc3E, 0x0000, 0x0000 } }, + { 0x10417, { 0xd801, 0xdc3F, 0x0000, 0x0000 } }, + { 0x10418, { 0xd801, 0xdc40, 0x0000, 0x0000 } }, + { 0x10419, { 0xd801, 0xdc41, 0x0000, 0x0000 } }, + { 0x1041A, { 0xd801, 0xdc42, 0x0000, 0x0000 } }, + { 0x1041B, { 0xd801, 0xdc43, 0x0000, 0x0000 } }, + { 0x1041C, { 0xd801, 0xdc44, 0x0000, 0x0000 } }, + { 0x1041D, { 0xd801, 0xdc45, 0x0000, 0x0000 } }, + { 0x1041E, { 0xd801, 0xdc46, 0x0000, 0x0000 } }, + { 0x1041F, { 0xd801, 0xdc47, 0x0000, 0x0000 } }, + { 0x10420, { 0xd801, 0xdc48, 0x0000, 0x0000 } }, + { 0x10421, { 0xd801, 0xdc49, 0x0000, 0x0000 } }, + { 0x10422, { 0xd801, 0xdc4A, 0x0000, 0x0000 } }, + { 0x10423, { 0xd801, 0xdc4B, 0x0000, 0x0000 } }, + { 0x10424, { 0xd801, 0xdc4C, 0x0000, 0x0000 } }, + { 0x10425, { 0xd801, 0xdc4D, 0x0000, 0x0000 } }, + { 0x1D400, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D401, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D402, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D403, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D404, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D405, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D406, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D407, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x1D408, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D409, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D40A, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D40B, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D40C, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D40D, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D40E, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D40F, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D410, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D411, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x1D412, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D413, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D414, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D415, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D416, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D417, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D418, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D419, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D434, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D435, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D436, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D437, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D438, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D439, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D43A, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D43B, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x1D43C, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D43D, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D43E, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D43F, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D440, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D441, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D442, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D443, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D444, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D445, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x1D446, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D447, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D448, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D449, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D44A, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D44B, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D44C, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D44D, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D468, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D469, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D46A, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D46B, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D46C, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D46D, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D46E, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D46F, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x1D470, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D471, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D472, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D473, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D474, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D475, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D476, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D477, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D478, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D479, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x1D47A, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D47B, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D47C, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D47D, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D47E, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D47F, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D480, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D481, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D49C, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D49E, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D49F, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4A2, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4A5, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4A6, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4A9, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4AA, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4AB, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4AC, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4AE, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4AF, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4B0, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4B1, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4B2, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4B3, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4B4, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4B5, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4D0, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4D1, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4D2, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4D3, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4D4, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4D5, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4D6, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4D7, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4D8, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4D9, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4DA, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4DB, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4DC, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4DD, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4DE, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4DF, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4E0, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4E1, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4E2, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4E3, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4E4, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4E5, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4E6, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4E7, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4E8, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D4E9, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D504, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D505, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D507, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D508, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D509, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D50A, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D50D, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D50E, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D50F, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D510, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D511, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D512, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D513, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D514, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D516, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D517, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D518, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D519, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D51A, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D51B, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D51C, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D538, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D539, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D53B, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D53C, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D53D, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D53E, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D540, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D541, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D542, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D543, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D544, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D546, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D54A, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D54B, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D54C, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D54D, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D54E, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D54F, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D550, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D56C, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D56D, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D56E, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D56F, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D570, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D571, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D572, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D573, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x1D574, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D575, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D576, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D577, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D578, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D579, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D57A, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D57B, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D57C, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D57D, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x1D57E, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D57F, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D580, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D581, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D582, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D583, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D584, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D585, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5A0, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5A1, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5A2, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5A3, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5A4, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5A5, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5A6, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5A7, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5A8, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5A9, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5AA, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5AB, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5AC, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5AD, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5AE, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5AF, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5B0, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5B1, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5B2, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5B3, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5B4, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5B5, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5B6, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5B7, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5B8, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5B9, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5D4, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5D5, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5D6, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5D7, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5D8, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5D9, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5DA, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5DB, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5DC, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5DD, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5DE, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5DF, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5E0, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5E1, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5E2, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5E3, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5E4, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5E5, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5E6, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5E7, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5E8, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5E9, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5EA, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5EB, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5EC, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D5ED, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D608, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D609, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D60A, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D60B, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D60C, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D60D, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D60E, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D60F, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x1D610, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D611, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D612, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D613, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D614, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D615, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D616, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D617, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D618, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D619, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x1D61A, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D61B, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D61C, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D61D, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D61E, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D61F, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D620, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D621, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D63C, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D63D, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D63E, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D63F, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D640, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D641, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D642, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D643, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x1D644, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D645, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D646, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D647, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D648, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D649, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D64A, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D64B, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D64C, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D64D, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x1D64E, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D64F, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D650, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D651, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D652, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D653, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D654, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D655, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D670, { 0x0061, 0x0000, 0x0000, 0x0000 } }, + { 0x1D671, { 0x0062, 0x0000, 0x0000, 0x0000 } }, + { 0x1D672, { 0x0063, 0x0000, 0x0000, 0x0000 } }, + { 0x1D673, { 0x0064, 0x0000, 0x0000, 0x0000 } }, + { 0x1D674, { 0x0065, 0x0000, 0x0000, 0x0000 } }, + { 0x1D675, { 0x0066, 0x0000, 0x0000, 0x0000 } }, + { 0x1D676, { 0x0067, 0x0000, 0x0000, 0x0000 } }, + { 0x1D677, { 0x0068, 0x0000, 0x0000, 0x0000 } }, + { 0x1D678, { 0x0069, 0x0000, 0x0000, 0x0000 } }, + { 0x1D679, { 0x006A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D67A, { 0x006B, 0x0000, 0x0000, 0x0000 } }, + { 0x1D67B, { 0x006C, 0x0000, 0x0000, 0x0000 } }, + { 0x1D67C, { 0x006D, 0x0000, 0x0000, 0x0000 } }, + { 0x1D67D, { 0x006E, 0x0000, 0x0000, 0x0000 } }, + { 0x1D67E, { 0x006F, 0x0000, 0x0000, 0x0000 } }, + { 0x1D67F, { 0x0070, 0x0000, 0x0000, 0x0000 } }, + { 0x1D680, { 0x0071, 0x0000, 0x0000, 0x0000 } }, + { 0x1D681, { 0x0072, 0x0000, 0x0000, 0x0000 } }, + { 0x1D682, { 0x0073, 0x0000, 0x0000, 0x0000 } }, + { 0x1D683, { 0x0074, 0x0000, 0x0000, 0x0000 } }, + { 0x1D684, { 0x0075, 0x0000, 0x0000, 0x0000 } }, + { 0x1D685, { 0x0076, 0x0000, 0x0000, 0x0000 } }, + { 0x1D686, { 0x0077, 0x0000, 0x0000, 0x0000 } }, + { 0x1D687, { 0x0078, 0x0000, 0x0000, 0x0000 } }, + { 0x1D688, { 0x0079, 0x0000, 0x0000, 0x0000 } }, + { 0x1D689, { 0x007A, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6A8, { 0x03B1, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6A9, { 0x03B2, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6AA, { 0x03B3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6AB, { 0x03B4, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6AC, { 0x03B5, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6AD, { 0x03B6, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6AE, { 0x03B7, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6AF, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6B0, { 0x03B9, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6B1, { 0x03BA, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6B2, { 0x03BB, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6B3, { 0x03BC, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6B4, { 0x03BD, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6B5, { 0x03BE, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6B6, { 0x03BF, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6B7, { 0x03C0, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6B8, { 0x03C1, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6B9, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6BA, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6BB, { 0x03C4, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6BC, { 0x03C5, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6BD, { 0x03C6, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6BE, { 0x03C7, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6BF, { 0x03C8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6C0, { 0x03C9, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6D3, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6E2, { 0x03B1, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6E3, { 0x03B2, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6E4, { 0x03B3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6E5, { 0x03B4, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6E6, { 0x03B5, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6E7, { 0x03B6, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6E8, { 0x03B7, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6E9, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6EA, { 0x03B9, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6EB, { 0x03BA, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6EC, { 0x03BB, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6ED, { 0x03BC, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6EE, { 0x03BD, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6EF, { 0x03BE, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6F0, { 0x03BF, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6F1, { 0x03C0, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6F2, { 0x03C1, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6F3, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6F4, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6F5, { 0x03C4, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6F6, { 0x03C5, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6F7, { 0x03C6, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6F8, { 0x03C7, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6F9, { 0x03C8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D6FA, { 0x03C9, 0x0000, 0x0000, 0x0000 } }, + { 0x1D70D, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D71C, { 0x03B1, 0x0000, 0x0000, 0x0000 } }, + { 0x1D71D, { 0x03B2, 0x0000, 0x0000, 0x0000 } }, + { 0x1D71E, { 0x03B3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D71F, { 0x03B4, 0x0000, 0x0000, 0x0000 } }, + { 0x1D720, { 0x03B5, 0x0000, 0x0000, 0x0000 } }, + { 0x1D721, { 0x03B6, 0x0000, 0x0000, 0x0000 } }, + { 0x1D722, { 0x03B7, 0x0000, 0x0000, 0x0000 } }, + { 0x1D723, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D724, { 0x03B9, 0x0000, 0x0000, 0x0000 } }, + { 0x1D725, { 0x03BA, 0x0000, 0x0000, 0x0000 } }, + { 0x1D726, { 0x03BB, 0x0000, 0x0000, 0x0000 } }, + { 0x1D727, { 0x03BC, 0x0000, 0x0000, 0x0000 } }, + { 0x1D728, { 0x03BD, 0x0000, 0x0000, 0x0000 } }, + { 0x1D729, { 0x03BE, 0x0000, 0x0000, 0x0000 } }, + { 0x1D72A, { 0x03BF, 0x0000, 0x0000, 0x0000 } }, + { 0x1D72B, { 0x03C0, 0x0000, 0x0000, 0x0000 } }, + { 0x1D72C, { 0x03C1, 0x0000, 0x0000, 0x0000 } }, + { 0x1D72D, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D72E, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D72F, { 0x03C4, 0x0000, 0x0000, 0x0000 } }, + { 0x1D730, { 0x03C5, 0x0000, 0x0000, 0x0000 } }, + { 0x1D731, { 0x03C6, 0x0000, 0x0000, 0x0000 } }, + { 0x1D732, { 0x03C7, 0x0000, 0x0000, 0x0000 } }, + { 0x1D733, { 0x03C8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D734, { 0x03C9, 0x0000, 0x0000, 0x0000 } }, + { 0x1D747, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D756, { 0x03B1, 0x0000, 0x0000, 0x0000 } }, + { 0x1D757, { 0x03B2, 0x0000, 0x0000, 0x0000 } }, + { 0x1D758, { 0x03B3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D759, { 0x03B4, 0x0000, 0x0000, 0x0000 } }, + { 0x1D75A, { 0x03B5, 0x0000, 0x0000, 0x0000 } }, + { 0x1D75B, { 0x03B6, 0x0000, 0x0000, 0x0000 } }, + { 0x1D75C, { 0x03B7, 0x0000, 0x0000, 0x0000 } }, + { 0x1D75D, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D75E, { 0x03B9, 0x0000, 0x0000, 0x0000 } }, + { 0x1D75F, { 0x03BA, 0x0000, 0x0000, 0x0000 } }, + { 0x1D760, { 0x03BB, 0x0000, 0x0000, 0x0000 } }, + { 0x1D761, { 0x03BC, 0x0000, 0x0000, 0x0000 } }, + { 0x1D762, { 0x03BD, 0x0000, 0x0000, 0x0000 } }, + { 0x1D763, { 0x03BE, 0x0000, 0x0000, 0x0000 } }, + { 0x1D764, { 0x03BF, 0x0000, 0x0000, 0x0000 } }, + { 0x1D765, { 0x03C0, 0x0000, 0x0000, 0x0000 } }, + { 0x1D766, { 0x03C1, 0x0000, 0x0000, 0x0000 } }, + { 0x1D767, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D768, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D769, { 0x03C4, 0x0000, 0x0000, 0x0000 } }, + { 0x1D76A, { 0x03C5, 0x0000, 0x0000, 0x0000 } }, + { 0x1D76B, { 0x03C6, 0x0000, 0x0000, 0x0000 } }, + { 0x1D76C, { 0x03C7, 0x0000, 0x0000, 0x0000 } }, + { 0x1D76D, { 0x03C8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D76E, { 0x03C9, 0x0000, 0x0000, 0x0000 } }, + { 0x1D781, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D790, { 0x03B1, 0x0000, 0x0000, 0x0000 } }, + { 0x1D791, { 0x03B2, 0x0000, 0x0000, 0x0000 } }, + { 0x1D792, { 0x03B3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D793, { 0x03B4, 0x0000, 0x0000, 0x0000 } }, + { 0x1D794, { 0x03B5, 0x0000, 0x0000, 0x0000 } }, + { 0x1D795, { 0x03B6, 0x0000, 0x0000, 0x0000 } }, + { 0x1D796, { 0x03B7, 0x0000, 0x0000, 0x0000 } }, + { 0x1D797, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D798, { 0x03B9, 0x0000, 0x0000, 0x0000 } }, + { 0x1D799, { 0x03BA, 0x0000, 0x0000, 0x0000 } }, + { 0x1D79A, { 0x03BB, 0x0000, 0x0000, 0x0000 } }, + { 0x1D79B, { 0x03BC, 0x0000, 0x0000, 0x0000 } }, + { 0x1D79C, { 0x03BD, 0x0000, 0x0000, 0x0000 } }, + { 0x1D79D, { 0x03BE, 0x0000, 0x0000, 0x0000 } }, + { 0x1D79E, { 0x03BF, 0x0000, 0x0000, 0x0000 } }, + { 0x1D79F, { 0x03C0, 0x0000, 0x0000, 0x0000 } }, + { 0x1D7A0, { 0x03C1, 0x0000, 0x0000, 0x0000 } }, + { 0x1D7A1, { 0x03B8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D7A2, { 0x03C3, 0x0000, 0x0000, 0x0000 } }, + { 0x1D7A3, { 0x03C4, 0x0000, 0x0000, 0x0000 } }, + { 0x1D7A4, { 0x03C5, 0x0000, 0x0000, 0x0000 } }, + { 0x1D7A5, { 0x03C6, 0x0000, 0x0000, 0x0000 } }, + { 0x1D7A6, { 0x03C7, 0x0000, 0x0000, 0x0000 } }, + { 0x1D7A7, { 0x03C8, 0x0000, 0x0000, 0x0000 } }, + { 0x1D7A8, { 0x03C9, 0x0000, 0x0000, 0x0000 } }, + { 0x1D7BB, { 0x03C3, 0x0000, 0x0000, 0x0000 } } +}; + +static void mapToLowerCase(QString *str, int from) +{ + int N = sizeof(NameprepCaseFolding) / sizeof(NameprepCaseFolding[0]); + + ushort *d = 0; + for (int i = from; i < str->size(); ++i) { + uint uc = str->at(i).unicode(); + if (uc < 0x80) { + if (uc <= 'Z' && uc >= 'A') { + if (!d) + d = reinterpret_cast<ushort *>(str->data()); + d[i] = (uc | 0x20); + } + } else { + if (QChar(uc).isHighSurrogate() && i < str->size() - 1) { + ushort low = str->at(i + 1).unicode(); + if (QChar(low).isLowSurrogate()) { + uc = QChar::surrogateToUcs4(uc, low); + ++i; + } + } + const NameprepCaseFoldingEntry *entry = qBinaryFind(NameprepCaseFolding, + NameprepCaseFolding + N, + uc); + if ((entry - NameprepCaseFolding) != N) { + int l = 1; + while (l < 4 && entry->mapping[l]) + ++l; + if (l > 1) { + if (uc <= 0xffff) + str->replace(i, 1, reinterpret_cast<const QChar *>(&entry->mapping[0]), l); + else + str->replace(i-1, 2, reinterpret_cast<const QChar *>(&entry->mapping[0]), l); + d = 0; + } else { + if (!d) + d = reinterpret_cast<ushort *>(str->data()); + d[i] = entry->mapping[0]; + } + } + } + } +} + +static bool isMappedToNothing(uint uc) +{ + if (uc < 0xad) + return false; + switch (uc) { + case 0x00AD: case 0x034F: case 0x1806: case 0x180B: case 0x180C: case 0x180D: + case 0x200B: case 0x200C: case 0x200D: case 0x2060: case 0xFE00: case 0xFE01: + case 0xFE02: case 0xFE03: case 0xFE04: case 0xFE05: case 0xFE06: case 0xFE07: + case 0xFE08: case 0xFE09: case 0xFE0A: case 0xFE0B: case 0xFE0C: case 0xFE0D: + case 0xFE0E: case 0xFE0F: case 0xFEFF: + return true; + default: + return false; + } +} + + +static void stripProhibitedOutput(QString *str, int from) +{ + ushort *out = (ushort *)str->data() + from; + const ushort *in = out; + const ushort *end = (ushort *)str->data() + str->size(); + while (in < end) { + uint uc = *in; + if (QChar(uc).isHighSurrogate() && in < end - 1) { + ushort low = *(in + 1); + if (QChar(low).isLowSurrogate()) { + ++in; + uc = QChar::surrogateToUcs4(uc, low); + } + } + if (uc <= 0xFFFF) { + if (uc < 0x80 || + !(uc <= 0x009F + || uc == 0x00A0 + || uc == 0x0340 + || uc == 0x0341 + || uc == 0x06DD + || uc == 0x070F + || uc == 0x1680 + || uc == 0x180E + || (uc >= 0x2000 && uc <= 0x200F) + || (uc >= 0x2028 && uc <= 0x202F) + || uc == 0x205F + || (uc >= 0x2060 && uc <= 0x2063) + || (uc >= 0x206A && uc <= 0x206F) + || (uc >= 0x2FF0 && uc <= 0x2FFB) + || uc == 0x3000 + || (uc >= 0xD800 && uc <= 0xDFFF) + || (uc >= 0xE000 && uc <= 0xF8FF) + || (uc >= 0xFDD0 && uc <= 0xFDEF) + || uc == 0xFEFF + || (uc >= 0xFFF9 && uc <= 0xFFFF))) { + *out++ = *in; + } + } else { + if (!((uc >= 0x1D173 && uc <= 0x1D17A) + || (uc >= 0x1FFFE && uc <= 0x1FFFF) + || (uc >= 0x2FFFE && uc <= 0x2FFFF) + || (uc >= 0x3FFFE && uc <= 0x3FFFF) + || (uc >= 0x4FFFE && uc <= 0x4FFFF) + || (uc >= 0x5FFFE && uc <= 0x5FFFF) + || (uc >= 0x6FFFE && uc <= 0x6FFFF) + || (uc >= 0x7FFFE && uc <= 0x7FFFF) + || (uc >= 0x8FFFE && uc <= 0x8FFFF) + || (uc >= 0x9FFFE && uc <= 0x9FFFF) + || (uc >= 0xAFFFE && uc <= 0xAFFFF) + || (uc >= 0xBFFFE && uc <= 0xBFFFF) + || (uc >= 0xCFFFE && uc <= 0xCFFFF) + || (uc >= 0xDFFFE && uc <= 0xDFFFF) + || uc == 0xE0001 + || (uc >= 0xE0020 && uc <= 0xE007F) + || (uc >= 0xEFFFE && uc <= 0xEFFFF) + || (uc >= 0xF0000 && uc <= 0xFFFFD) + || (uc >= 0xFFFFE && uc <= 0xFFFFF) + || (uc >= 0x100000 && uc <= 0x10FFFD) + || (uc >= 0x10FFFE && uc <= 0x10FFFF))) { + *out++ = QChar::highSurrogate(uc); + *out++ = QChar::lowSurrogate(uc); + } + } + ++in; + } + if (in != out) + str->truncate(out - str->utf16()); +} + +static bool isBidirectionalRorAL(uint uc) +{ + if (uc < 0x5b0) + return false; + return uc == 0x05BE + || uc == 0x05C0 + || uc == 0x05C3 + || (uc >= 0x05D0 && uc <= 0x05EA) + || (uc >= 0x05F0 && uc <= 0x05F4) + || uc == 0x061B + || uc == 0x061F + || (uc >= 0x0621 && uc <= 0x063A) + || (uc >= 0x0640 && uc <= 0x064A) + || (uc >= 0x066D && uc <= 0x066F) + || (uc >= 0x0671 && uc <= 0x06D5) + || uc == 0x06DD + || (uc >= 0x06E5 && uc <= 0x06E6) + || (uc >= 0x06FA && uc <= 0x06FE) + || (uc >= 0x0700 && uc <= 0x070D) + || uc == 0x0710 + || (uc >= 0x0712 && uc <= 0x072C) + || (uc >= 0x0780 && uc <= 0x07A5) + || uc == 0x07B1 + || uc == 0x200F + || uc == 0xFB1D + || (uc >= 0xFB1F && uc <= 0xFB28) + || (uc >= 0xFB2A && uc <= 0xFB36) + || (uc >= 0xFB38 && uc <= 0xFB3C) + || uc == 0xFB3E + || (uc >= 0xFB40 && uc <= 0xFB41) + || (uc >= 0xFB43 && uc <= 0xFB44) + || (uc >= 0xFB46 && uc <= 0xFBB1) + || (uc >= 0xFBD3 && uc <= 0xFD3D) + || (uc >= 0xFD50 && uc <= 0xFD8F) + || (uc >= 0xFD92 && uc <= 0xFDC7) + || (uc >= 0xFDF0 && uc <= 0xFDFC) + || (uc >= 0xFE70 && uc <= 0xFE74) + || (uc >= 0xFE76 && uc <= 0xFEFC); +} + +static bool isBidirectionalL(uint uc) +{ + if (uc < 0xaa) + return (uc >= 0x0041 && uc <= 0x005A) + || (uc >= 0x0061 && uc <= 0x007A); + + if (uc == 0x00AA + || uc == 0x00B5 + || uc == 0x00BA + || (uc >= 0x00C0 && uc <= 0x00D6) + || (uc >= 0x00D8 && uc <= 0x00F6) + || (uc >= 0x00F8 && uc <= 0x0220) + || (uc >= 0x0222 && uc <= 0x0233) + || (uc >= 0x0250 && uc <= 0x02AD) + || (uc >= 0x02B0 && uc <= 0x02B8) + || (uc >= 0x02BB && uc <= 0x02C1) + || (uc >= 0x02D0 && uc <= 0x02D1) + || (uc >= 0x02E0 && uc <= 0x02E4) + || uc == 0x02EE + || uc == 0x037A + || uc == 0x0386 + || (uc >= 0x0388 && uc <= 0x038A)) { + return true; + } + + if (uc == 0x038C + || (uc >= 0x038E && uc <= 0x03A1) + || (uc >= 0x03A3 && uc <= 0x03CE) + || (uc >= 0x03D0 && uc <= 0x03F5) + || (uc >= 0x0400 && uc <= 0x0482) + || (uc >= 0x048A && uc <= 0x04CE) + || (uc >= 0x04D0 && uc <= 0x04F5) + || (uc >= 0x04F8 && uc <= 0x04F9) + || (uc >= 0x0500 && uc <= 0x050F) + || (uc >= 0x0531 && uc <= 0x0556) + || (uc >= 0x0559 && uc <= 0x055F) + || (uc >= 0x0561 && uc <= 0x0587) + || uc == 0x0589 + || uc == 0x0903 + || (uc >= 0x0905 && uc <= 0x0939) + || (uc >= 0x093D && uc <= 0x0940) + || (uc >= 0x0949 && uc <= 0x094C) + || uc == 0x0950) { + return true; + } + + if ((uc >= 0x0958 && uc <= 0x0961) + || (uc >= 0x0964 && uc <= 0x0970) + || (uc >= 0x0982 && uc <= 0x0983) + || (uc >= 0x0985 && uc <= 0x098C) + || (uc >= 0x098F && uc <= 0x0990) + || (uc >= 0x0993 && uc <= 0x09A8) + || (uc >= 0x09AA && uc <= 0x09B0) + || uc == 0x09B2 + || (uc >= 0x09B6 && uc <= 0x09B9) + || (uc >= 0x09BE && uc <= 0x09C0) + || (uc >= 0x09C7 && uc <= 0x09C8) + || (uc >= 0x09CB && uc <= 0x09CC) + || uc == 0x09D7 + || (uc >= 0x09DC && uc <= 0x09DD) + || (uc >= 0x09DF && uc <= 0x09E1) + || (uc >= 0x09E6 && uc <= 0x09F1) + || (uc >= 0x09F4 && uc <= 0x09FA) + || (uc >= 0x0A05 && uc <= 0x0A0A) + || (uc >= 0x0A0F && uc <= 0x0A10) + || (uc >= 0x0A13 && uc <= 0x0A28) + || (uc >= 0x0A2A && uc <= 0x0A30) + || (uc >= 0x0A32 && uc <= 0x0A33)) { + return true; + } + + if ((uc >= 0x0A35 && uc <= 0x0A36) + || (uc >= 0x0A38 && uc <= 0x0A39) + || (uc >= 0x0A3E && uc <= 0x0A40) + || (uc >= 0x0A59 && uc <= 0x0A5C) + || uc == 0x0A5E + || (uc >= 0x0A66 && uc <= 0x0A6F) + || (uc >= 0x0A72 && uc <= 0x0A74) + || uc == 0x0A83 + || (uc >= 0x0A85 && uc <= 0x0A8B) + || uc == 0x0A8D + || (uc >= 0x0A8F && uc <= 0x0A91) + || (uc >= 0x0A93 && uc <= 0x0AA8) + || (uc >= 0x0AAA && uc <= 0x0AB0) + || (uc >= 0x0AB2 && uc <= 0x0AB3) + || (uc >= 0x0AB5 && uc <= 0x0AB9) + || (uc >= 0x0ABD && uc <= 0x0AC0) + || uc == 0x0AC9 + || (uc >= 0x0ACB && uc <= 0x0ACC) + || uc == 0x0AD0 + || uc == 0x0AE0 + || (uc >= 0x0AE6 && uc <= 0x0AEF) + || (uc >= 0x0B02 && uc <= 0x0B03) + || (uc >= 0x0B05 && uc <= 0x0B0C) + || (uc >= 0x0B0F && uc <= 0x0B10) + || (uc >= 0x0B13 && uc <= 0x0B28) + || (uc >= 0x0B2A && uc <= 0x0B30)) { + return true; + } + + if ((uc >= 0x0B32 && uc <= 0x0B33) + || (uc >= 0x0B36 && uc <= 0x0B39) + || (uc >= 0x0B3D && uc <= 0x0B3E) + || uc == 0x0B40 + || (uc >= 0x0B47 && uc <= 0x0B48) + || (uc >= 0x0B4B && uc <= 0x0B4C) + || uc == 0x0B57 + || (uc >= 0x0B5C && uc <= 0x0B5D) + || (uc >= 0x0B5F && uc <= 0x0B61) + || (uc >= 0x0B66 && uc <= 0x0B70) + || uc == 0x0B83 + || (uc >= 0x0B85 && uc <= 0x0B8A) + || (uc >= 0x0B8E && uc <= 0x0B90) + || (uc >= 0x0B92 && uc <= 0x0B95) + || (uc >= 0x0B99 && uc <= 0x0B9A) + || uc == 0x0B9C + || (uc >= 0x0B9E && uc <= 0x0B9F) + || (uc >= 0x0BA3 && uc <= 0x0BA4) + || (uc >= 0x0BA8 && uc <= 0x0BAA) + || (uc >= 0x0BAE && uc <= 0x0BB5) + || (uc >= 0x0BB7 && uc <= 0x0BB9) + || (uc >= 0x0BBE && uc <= 0x0BBF) + || (uc >= 0x0BC1 && uc <= 0x0BC2) + || (uc >= 0x0BC6 && uc <= 0x0BC8) + || (uc >= 0x0BCA && uc <= 0x0BCC) + || uc == 0x0BD7 + || (uc >= 0x0BE7 && uc <= 0x0BF2) + || (uc >= 0x0C01 && uc <= 0x0C03) + || (uc >= 0x0C05 && uc <= 0x0C0C) + || (uc >= 0x0C0E && uc <= 0x0C10) + || (uc >= 0x0C12 && uc <= 0x0C28) + || (uc >= 0x0C2A && uc <= 0x0C33) + || (uc >= 0x0C35 && uc <= 0x0C39)) { + return true; + } + if ((uc >= 0x0C41 && uc <= 0x0C44) + || (uc >= 0x0C60 && uc <= 0x0C61) + || (uc >= 0x0C66 && uc <= 0x0C6F) + || (uc >= 0x0C82 && uc <= 0x0C83) + || (uc >= 0x0C85 && uc <= 0x0C8C) + || (uc >= 0x0C8E && uc <= 0x0C90) + || (uc >= 0x0C92 && uc <= 0x0CA8) + || (uc >= 0x0CAA && uc <= 0x0CB3) + || (uc >= 0x0CB5 && uc <= 0x0CB9) + || uc == 0x0CBE + || (uc >= 0x0CC0 && uc <= 0x0CC4) + || (uc >= 0x0CC7 && uc <= 0x0CC8) + || (uc >= 0x0CCA && uc <= 0x0CCB) + || (uc >= 0x0CD5 && uc <= 0x0CD6) + || uc == 0x0CDE + || (uc >= 0x0CE0 && uc <= 0x0CE1) + || (uc >= 0x0CE6 && uc <= 0x0CEF) + || (uc >= 0x0D02 && uc <= 0x0D03) + || (uc >= 0x0D05 && uc <= 0x0D0C) + || (uc >= 0x0D0E && uc <= 0x0D10) + || (uc >= 0x0D12 && uc <= 0x0D28) + || (uc >= 0x0D2A && uc <= 0x0D39) + || (uc >= 0x0D3E && uc <= 0x0D40) + || (uc >= 0x0D46 && uc <= 0x0D48) + || (uc >= 0x0D4A && uc <= 0x0D4C) + || uc == 0x0D57 + || (uc >= 0x0D60 && uc <= 0x0D61) + || (uc >= 0x0D66 && uc <= 0x0D6F) + || (uc >= 0x0D82 && uc <= 0x0D83) + || (uc >= 0x0D85 && uc <= 0x0D96) + || (uc >= 0x0D9A && uc <= 0x0DB1) + || (uc >= 0x0DB3 && uc <= 0x0DBB) + || uc == 0x0DBD) { + return true; + } + if ((uc >= 0x0DC0 && uc <= 0x0DC6) + || (uc >= 0x0DCF && uc <= 0x0DD1) + || (uc >= 0x0DD8 && uc <= 0x0DDF) + || (uc >= 0x0DF2 && uc <= 0x0DF4) + || (uc >= 0x0E01 && uc <= 0x0E30) + || (uc >= 0x0E32 && uc <= 0x0E33) + || (uc >= 0x0E40 && uc <= 0x0E46) + || (uc >= 0x0E4F && uc <= 0x0E5B) + || (uc >= 0x0E81 && uc <= 0x0E82) + || uc == 0x0E84 + || (uc >= 0x0E87 && uc <= 0x0E88) + || uc == 0x0E8A + || uc == 0x0E8D + || (uc >= 0x0E94 && uc <= 0x0E97) + || (uc >= 0x0E99 && uc <= 0x0E9F) + || (uc >= 0x0EA1 && uc <= 0x0EA3) + || uc == 0x0EA5 + || uc == 0x0EA7 + || (uc >= 0x0EAA && uc <= 0x0EAB) + || (uc >= 0x0EAD && uc <= 0x0EB0) + || (uc >= 0x0EB2 && uc <= 0x0EB3) + || uc == 0x0EBD + || (uc >= 0x0EC0 && uc <= 0x0EC4) + || uc == 0x0EC6 + || (uc >= 0x0ED0 && uc <= 0x0ED9) + || (uc >= 0x0EDC && uc <= 0x0EDD) + || (uc >= 0x0F00 && uc <= 0x0F17) + || (uc >= 0x0F1A && uc <= 0x0F34) + || uc == 0x0F36 + || uc == 0x0F38 + || (uc >= 0x0F3E && uc <= 0x0F47) + || (uc >= 0x0F49 && uc <= 0x0F6A) + || uc == 0x0F7F + || uc == 0x0F85 + || (uc >= 0x0F88 && uc <= 0x0F8B) + || (uc >= 0x0FBE && uc <= 0x0FC5) + || (uc >= 0x0FC7 && uc <= 0x0FCC) + || uc == 0x0FCF) { + return true; + } + + if ((uc >= 0x1000 && uc <= 0x1021) + || (uc >= 0x1023 && uc <= 0x1027) + || (uc >= 0x1029 && uc <= 0x102A) + || uc == 0x102C + || uc == 0x1031 + || uc == 0x1038 + || (uc >= 0x1040 && uc <= 0x1057) + || (uc >= 0x10A0 && uc <= 0x10C5) + || (uc >= 0x10D0 && uc <= 0x10F8) + || uc == 0x10FB + || (uc >= 0x1100 && uc <= 0x1159) + || (uc >= 0x115F && uc <= 0x11A2) + || (uc >= 0x11A8 && uc <= 0x11F9) + || (uc >= 0x1200 && uc <= 0x1206) + || (uc >= 0x1208 && uc <= 0x1246) + || uc == 0x1248 + || (uc >= 0x124A && uc <= 0x124D) + || (uc >= 0x1250 && uc <= 0x1256) + || uc == 0x1258 + || (uc >= 0x125A && uc <= 0x125D) + || (uc >= 0x1260 && uc <= 0x1286) + || uc == 0x1288 + || (uc >= 0x128A && uc <= 0x128D) + || (uc >= 0x1290 && uc <= 0x12AE) + || uc == 0x12B0 + || (uc >= 0x12B2 && uc <= 0x12B5) + || (uc >= 0x12B8 && uc <= 0x12BE) + || uc == 0x12C0 + || (uc >= 0x12C2 && uc <= 0x12C5) + || (uc >= 0x12C8 && uc <= 0x12CE) + || (uc >= 0x12D0 && uc <= 0x12D6) + || (uc >= 0x12D8 && uc <= 0x12EE) + || (uc >= 0x12F0 && uc <= 0x130E) + || uc == 0x1310) { + return true; + } + + if ((uc >= 0x1312 && uc <= 0x1315) + || (uc >= 0x1318 && uc <= 0x131E) + || (uc >= 0x1320 && uc <= 0x1346) + || (uc >= 0x1348 && uc <= 0x135A) + || (uc >= 0x1361 && uc <= 0x137C) + || (uc >= 0x13A0 && uc <= 0x13F4) + || (uc >= 0x1401 && uc <= 0x1676) + || (uc >= 0x1681 && uc <= 0x169A) + || (uc >= 0x16A0 && uc <= 0x16F0) + || (uc >= 0x1700 && uc <= 0x170C) + || (uc >= 0x170E && uc <= 0x1711) + || (uc >= 0x1720 && uc <= 0x1731) + || (uc >= 0x1735 && uc <= 0x1736) + || (uc >= 0x1740 && uc <= 0x1751) + || (uc >= 0x1760 && uc <= 0x176C) + || (uc >= 0x176E && uc <= 0x1770) + || (uc >= 0x1780 && uc <= 0x17B6) + || (uc >= 0x17BE && uc <= 0x17C5) + || (uc >= 0x17C7 && uc <= 0x17C8) + || (uc >= 0x17D4 && uc <= 0x17DA) + || uc == 0x17DC + || (uc >= 0x17E0 && uc <= 0x17E9) + || (uc >= 0x1810 && uc <= 0x1819) + || (uc >= 0x1820 && uc <= 0x1877) + || (uc >= 0x1880 && uc <= 0x18A8) + || (uc >= 0x1E00 && uc <= 0x1E9B) + || (uc >= 0x1EA0 && uc <= 0x1EF9) + || (uc >= 0x1F00 && uc <= 0x1F15) + || (uc >= 0x1F18 && uc <= 0x1F1D) + || (uc >= 0x1F20 && uc <= 0x1F45) + || (uc >= 0x1F48 && uc <= 0x1F4D) + || (uc >= 0x1F50 && uc <= 0x1F57) + || uc == 0x1F59 + || uc == 0x1F5B + || uc == 0x1F5D) { + return true; + } + + if ((uc >= 0x1F5F && uc <= 0x1F7D) + || (uc >= 0x1F80 && uc <= 0x1FB4) + || (uc >= 0x1FB6 && uc <= 0x1FBC) + || uc == 0x1FBE + || (uc >= 0x1FC2 && uc <= 0x1FC4) + || (uc >= 0x1FC6 && uc <= 0x1FCC) + || (uc >= 0x1FD0 && uc <= 0x1FD3) + || (uc >= 0x1FD6 && uc <= 0x1FDB) + || (uc >= 0x1FE0 && uc <= 0x1FEC) + || (uc >= 0x1FF2 && uc <= 0x1FF4) + || (uc >= 0x1FF6 && uc <= 0x1FFC) + || uc == 0x200E + || uc == 0x2071 + || uc == 0x207F + || uc == 0x2102 + || uc == 0x2107 + || (uc >= 0x210A && uc <= 0x2113) + || uc == 0x2115 + || (uc >= 0x2119 && uc <= 0x211D)) { + return true; + } + + if (uc == 0x2124 + || uc == 0x2126 + || uc == 0x2128 + || (uc >= 0x212A && uc <= 0x212D) + || (uc >= 0x212F && uc <= 0x2131) + || (uc >= 0x2133 && uc <= 0x2139) + || (uc >= 0x213D && uc <= 0x213F) + || (uc >= 0x2145 && uc <= 0x2149) + || (uc >= 0x2160 && uc <= 0x2183) + || (uc >= 0x2336 && uc <= 0x237A) + || uc == 0x2395 + || (uc >= 0x249C && uc <= 0x24E9) + || (uc >= 0x3005 && uc <= 0x3007) + || (uc >= 0x3021 && uc <= 0x3029) + || (uc >= 0x3031 && uc <= 0x3035) + || (uc >= 0x3038 && uc <= 0x303C) + || (uc >= 0x3041 && uc <= 0x3096) + || (uc >= 0x309D && uc <= 0x309F) + || (uc >= 0x30A1 && uc <= 0x30FA)) { + return true; + } + + if ((uc >= 0x30FC && uc <= 0x30FF) + || (uc >= 0x3105 && uc <= 0x312C) + || (uc >= 0x3131 && uc <= 0x318E) + || (uc >= 0x3190 && uc <= 0x31B7) + || (uc >= 0x31F0 && uc <= 0x321C) + || (uc >= 0x3220 && uc <= 0x3243)) { + return true; + } + + if ((uc >= 0x3260 && uc <= 0x327B) + || (uc >= 0x327F && uc <= 0x32B0) + || (uc >= 0x32C0 && uc <= 0x32CB) + || (uc >= 0x32D0 && uc <= 0x32FE) + || (uc >= 0x3300 && uc <= 0x3376) + || (uc >= 0x337B && uc <= 0x33DD)) { + return true; + } + if ((uc >= 0x33E0 && uc <= 0x33FE) + || (uc >= 0x3400 && uc <= 0x4DB5) + || (uc >= 0x4E00 && uc <= 0x9FA5) + || (uc >= 0xA000 && uc <= 0xA48C) + || (uc >= 0xAC00 && uc <= 0xD7A3) + || (uc >= 0xD800 && uc <= 0xFA2D) + || (uc >= 0xFA30 && uc <= 0xFA6A) + || (uc >= 0xFB00 && uc <= 0xFB06) + || (uc >= 0xFB13 && uc <= 0xFB17) + || (uc >= 0xFF21 && uc <= 0xFF3A) + || (uc >= 0xFF41 && uc <= 0xFF5A) + || (uc >= 0xFF66 && uc <= 0xFFBE) + || (uc >= 0xFFC2 && uc <= 0xFFC7) + || (uc >= 0xFFCA && uc <= 0xFFCF) + || (uc >= 0xFFD2 && uc <= 0xFFD7) + || (uc >= 0xFFDA && uc <= 0xFFDC)) { + return true; + } + + if ((uc >= 0x10300 && uc <= 0x1031E) + || (uc >= 0x10320 && uc <= 0x10323) + || (uc >= 0x10330 && uc <= 0x1034A) + || (uc >= 0x10400 && uc <= 0x10425) + || (uc >= 0x10428 && uc <= 0x1044D) + || (uc >= 0x1D000 && uc <= 0x1D0F5) + || (uc >= 0x1D100 && uc <= 0x1D126) + || (uc >= 0x1D12A && uc <= 0x1D166) + || (uc >= 0x1D16A && uc <= 0x1D172) + || (uc >= 0x1D183 && uc <= 0x1D184) + || (uc >= 0x1D18C && uc <= 0x1D1A9) + || (uc >= 0x1D1AE && uc <= 0x1D1DD) + || (uc >= 0x1D400 && uc <= 0x1D454) + || (uc >= 0x1D456 && uc <= 0x1D49C) + || (uc >= 0x1D49E && uc <= 0x1D49F) + || uc == 0x1D4A2 + || (uc >= 0x1D4A5 && uc <= 0x1D4A6) + || (uc >= 0x1D4A9 && uc <= 0x1D4AC) + || (uc >= 0x1D4AE && uc <= 0x1D4B9) + || uc == 0x1D4BB + || (uc >= 0x1D4BD && uc <= 0x1D4C0) + || (uc >= 0x1D4C2 && uc <= 0x1D4C3) + || (uc >= 0x1D4C5 && uc <= 0x1D505) + || (uc >= 0x1D507 && uc <= 0x1D50A) + || (uc >= 0x1D50D && uc <= 0x1D514) + || (uc >= 0x1D516 && uc <= 0x1D51C) + || (uc >= 0x1D51E && uc <= 0x1D539) + || (uc >= 0x1D53B && uc <= 0x1D53E) + || (uc >= 0x1D540 && uc <= 0x1D544) + || uc == 0x1D546 + || (uc >= 0x1D54A && uc <= 0x1D550) + || (uc >= 0x1D552 && uc <= 0x1D6A3) + || (uc >= 0x1D6A8 && uc <= 0x1D7C9) + || (uc >= 0x20000 && uc <= 0x2A6D6) + || (uc >= 0x2F800 && uc <= 0x2FA1D) + || (uc >= 0xF0000 && uc <= 0xFFFFD) + || (uc >= 0x100000 && uc <= 0x10FFFD)) { + return true; + } + + return false; +} + +#ifdef QT_BUILD_INTERNAL +// export for tst_qurl.cpp +Q_AUTOTEST_EXPORT void qt_nameprep(QString *source, int from); +Q_AUTOTEST_EXPORT bool qt_check_std3rules(const QChar *uc, int len); +#else +// non-test build, keep the symbols for ourselves +static void qt_nameprep(QString *source, int from); +static bool qt_check_std3rules(const QChar *uc, int len); +#endif + +void qt_nameprep(QString *source, int from) +{ + QChar *src = source->data(); // causes a detach, so we're sure the only one using it + QChar *out = src + from; + const QChar *e = src + source->size(); + + for ( ; out < e; ++out) { + register ushort uc = out->unicode(); + if (uc > 0x80) { + break; + } else if (uc >= 'A' && uc <= 'Z') { + *out = QChar(uc | 0x20); + } + } + if (out == e) + return; // everything was mapped easily (lowercased, actually) + int firstNonAscii = out - src; + + // Characters unassigned in Unicode 3.2 are not allowed in "stored string" scheme + // but allowed in "query" scheme + // (Table A.1) + const bool isUnassignedAllowed = false; // ### + // Characters commonly mapped to nothing are simply removed + // (Table B.1) + const QChar *in = out; + for ( ; in < e; ++in) { + uint uc = in->unicode(); + if (QChar(uc).isHighSurrogate() && in < e - 1) { + ushort low = in[1].unicode(); + if (QChar(low).isLowSurrogate()) { + ++in; + uc = QChar::surrogateToUcs4(uc, low); + } + } + if (!isUnassignedAllowed) { + QChar::UnicodeVersion version = QChar::unicodeVersion(uc); + if (version == QChar::Unicode_Unassigned || version > QChar::Unicode_3_2) { + source->resize(from); // not allowed, clear the label + return; + } + } + if (!isMappedToNothing(uc)) { + if (uc <= 0xFFFF) { + *out++ = *in; + } else { + *out++ = QChar::highSurrogate(uc); + *out++ = QChar::lowSurrogate(uc); + } + } + } + if (out != in) + source->truncate(out - src); + + // Map to lowercase (Table B.2) + mapToLowerCase(source, firstNonAscii); + + // Normalize to Unicode 3.2 form KC + extern void qt_string_normalize(QString *data, QString::NormalizationForm mode, + QChar::UnicodeVersion version, int from); + qt_string_normalize(source, QString::NormalizationForm_KC, QChar::Unicode_3_2, + firstNonAscii > from ? firstNonAscii - 1 : from); + + // Strip prohibited output + stripProhibitedOutput(source, firstNonAscii); + + // Check for valid bidirectional characters + bool containsLCat = false; + bool containsRandALCat = false; + src = source->data(); + e = src + source->size(); + for (in = src + from; in < e && (!containsLCat || !containsRandALCat); ++in) { + uint uc = in->unicode(); + if (QChar(uc).isHighSurrogate() && in < e - 1) { + ushort low = in[1].unicode(); + if (QChar(low).isLowSurrogate()) { + ++in; + uc = QChar::surrogateToUcs4(uc, low); + } + } + if (isBidirectionalL(uc)) + containsLCat = true; + else if (isBidirectionalRorAL(uc)) + containsRandALCat = true; + } + if (containsRandALCat) { + if (containsLCat || (!isBidirectionalRorAL(src[from].unicode()) + || !isBidirectionalRorAL(e[-1].unicode()))) + source->resize(from); // not allowed, clear the label + } +} + +bool qt_check_std3rules(const QChar *uc, int len) +{ + if (len > 63) + return false; + + for (int i = 0; i < len; ++i) { + register ushort c = uc[i].unicode(); + if (c == '-' && (i == 0 || i == len - 1)) + return false; + + // verifying the absence of LDH is the same as verifying that + // only LDH is present + if (c == '-' || (c >= '0' && c <= '9') + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + //underscore is not supposed to be allowed, but other browser accept it (QTBUG-7434) + || c == '_') + continue; + + return false; + } + + return true; +} + + +static inline uint encodeDigit(uint digit) +{ + return digit + 22 + 75 * (digit < 26); +} + +static inline uint adapt(uint delta, uint numpoints, bool firsttime) +{ + delta /= (firsttime ? damp : 2); + delta += (delta / numpoints); + + uint k = 0; + for (; delta > ((base - tmin) * tmax) / 2; k += base) + delta /= (base - tmin); + + return k + (((base - tmin + 1) * delta) / (delta + skew)); +} + +static inline void appendEncode(QString* output, uint& delta, uint& bias, uint& b, uint& h) +{ + uint qq; + uint k; + uint t; + + // insert the variable length delta integer; fail on + // overflow. + for (qq = delta, k = base;; k += base) { + // stop generating digits when the threshold is + // detected. + t = (k <= bias) ? tmin : (k >= bias + tmax) ? tmax : k - bias; + if (qq < t) break; + + *output += QChar(encodeDigit(t + (qq - t) % (base - t))); + qq = (qq - t) / (base - t); + } + + *output += QChar(encodeDigit(qq)); + bias = adapt(delta, h + 1, h == b); + delta = 0; + ++h; +} + +static void toPunycodeHelper(const QChar *s, int ucLength, QString *output) +{ + uint n = initial_n; + uint delta = 0; + uint bias = initial_bias; + + int outLen = output->length(); + output->resize(outLen + ucLength); + + QChar *d = output->data() + outLen; + bool skipped = false; + // copy all basic code points verbatim to output. + for (uint j = 0; j < (uint) ucLength; ++j) { + ushort js = s[j].unicode(); + if (js < 0x80) + *d++ = js; + else + skipped = true; + } + + // if there were only basic code points, just return them + // directly; don't do any encoding. + if (!skipped) + return; + + output->truncate(d - output->constData()); + int copied = output->size() - outLen; + + // h and b now contain the number of basic code points in input. + uint b = copied; + uint h = copied; + + // if basic code points were copied, add the delimiter character. + if (h > 0) + *output += QChar(0x2d); + + // while there are still unprocessed non-basic code points left in + // the input string... + while (h < (uint) ucLength) { + // find the character in the input string with the lowest + // unicode value. + uint m = Q_MAXINT; + uint j; + for (j = 0; j < (uint) ucLength; ++j) { + if (s[j].unicode() >= n && s[j].unicode() < m) + m = (uint) s[j].unicode(); + } + + // reject out-of-bounds unicode characters + if (m - n > (Q_MAXINT - delta) / (h + 1)) { + output->truncate(outLen); + return; // punycode_overflow + } + + delta += (m - n) * (h + 1); + n = m; + + // for each code point in the input string + for (j = 0; j < (uint) ucLength; ++j) { + + // increase delta until we reach the character with the + // lowest unicode code. fail if delta overflows. + if (s[j].unicode() < n) { + ++delta; + if (!delta) { + output->truncate(outLen); + return; // punycode_overflow + } + } + + // if j is the index of the character with the lowest + // unicode code... + if (s[j].unicode() == n) { + appendEncode(output, delta, bias, b, h); + } + } + + ++delta; + ++n; + } + + // prepend ACE prefix + output->insert(outLen, QLatin1String("xn--")); + return; +} + + +static const char * const idn_whitelist[] = { + "ac", "ar", "at", + "biz", "br", + "cat", "ch", "cl", "cn", + "de", "dk", + "es", + "fi", + "gr", + "hu", + "info", "io", "is", + "jp", + "kr", + "li", "lt", + "museum", + "no", + "org", + "se", "sh", + "th", "tm", "tw", + "vn", + "xn--mgbaam7a8h", // UAE + "xn--mgberp4a5d4ar", // Saudi Arabia + "xn--wgbh1c" // Egypt +}; + +static QStringList *user_idn_whitelist = 0; + +static bool lessThan(const QChar *a, int l, const char *c) +{ + const ushort *uc = (const ushort *)a; + const ushort *e = uc + l; + + if (!c || *c == 0) + return false; + + while (*c) { + if (uc == e || *uc != *c) + break; + ++uc; + ++c; + } + return (uc == e ? *c : *uc < *c); +} + +static bool equal(const QChar *a, int l, const char *b) +{ + while (l && a->unicode() && *b) { + if (*a != QLatin1Char(*b)) + return false; + ++a; + ++b; + --l; + } + return l == 0; +} + +static bool qt_is_idn_enabled(const QString &domain) +{ + int idx = domain.lastIndexOf(QLatin1Char('.')); + if (idx == -1) + return false; + + int len = domain.size() - idx - 1; + QString tldString(domain.constData() + idx + 1, len); + qt_nameprep(&tldString, 0); + + const QChar *tld = tldString.constData(); + + if (user_idn_whitelist) + return user_idn_whitelist->contains(tldString); + + int l = 0; + int r = sizeof(idn_whitelist)/sizeof(const char *) - 1; + int i = (l + r + 1) / 2; + + while (r != l) { + if (lessThan(tld, len, idn_whitelist[i])) + r = i - 1; + else + l = i; + i = (l + r + 1) / 2; + } + return equal(tld, len, idn_whitelist[i]); +} + +static inline bool isDotDelimiter(ushort uc) +{ + // IDNA / rfc3490 describes these four delimiters used for + // separating labels in unicode international domain + // names. + return uc == 0x2e || uc == 0x3002 || uc == 0xff0e || uc == 0xff61; +} + +static int nextDotDelimiter(const QString &domain, int from = 0) +{ + const QChar *b = domain.unicode(); + const QChar *ch = b + from; + const QChar *e = b + domain.length(); + while (ch < e) { + if (isDotDelimiter(ch->unicode())) + break; + else + ++ch; + } + return ch - b; +} + +enum AceOperation { ToAceOnly, NormalizeAce }; +static QString qt_ACE_do(const QString &domain, AceOperation op) +{ + if (domain.isEmpty()) + return domain; + + QString result; + result.reserve(domain.length()); + + const bool isIdnEnabled = op == NormalizeAce ? qt_is_idn_enabled(domain) : false; + int lastIdx = 0; + QString aceForm; // this variable is here for caching + + while (1) { + int idx = nextDotDelimiter(domain, lastIdx); + int labelLength = idx - lastIdx; + if (labelLength == 0) { + if (idx == domain.length()) + break; + return QString(); // two delimiters in a row -- empty label not allowed + } + + // RFC 3490 says, about the ToASCII operation: + // 3. If the UseSTD3ASCIIRules flag is set, then perform these checks: + // + // (a) Verify the absence of non-LDH ASCII code points; that is, the + // absence of 0..2C, 2E..2F, 3A..40, 5B..60, and 7B..7F. + // + // (b) Verify the absence of leading and trailing hyphen-minus; that + // is, the absence of U+002D at the beginning and end of the + // sequence. + // and: + // 8. Verify that the number of code points is in the range 1 to 63 + // inclusive. + + // copy the label to the destination, which also serves as our scratch area, lowercasing it + int prevLen = result.size(); + bool simple = true; + result.resize(prevLen + labelLength); + { + QChar *out = result.data() + prevLen; + const QChar *in = domain.constData() + lastIdx; + const QChar *e = in + labelLength; + for (; in < e; ++in, ++out) { + register ushort uc = in->unicode(); + if (uc > 0x7f) + simple = false; + if (uc >= 'A' && uc <= 'Z') + *out = QChar(uc | 0x20); + else + *out = *in; + } + } + + if (simple && labelLength > 6) { + // ACE form domains contain only ASCII characters, but we can't consider them simple + // is this an ACE form? + // the shortest valid ACE domain is 6 characters long (U+0080 would be 1, but it's not allowed) + static const ushort acePrefixUtf16[] = { 'x', 'n', '-', '-' }; + if (memcmp(result.constData() + prevLen, acePrefixUtf16, sizeof acePrefixUtf16) == 0) + simple = false; + } + + if (simple) { + // fastest case: this is the common case (non IDN-domains) + // so we're done + if (!qt_check_std3rules(result.constData() + prevLen, labelLength)) + return QString(); + } else { + // Punycode encoding and decoding cannot be done in-place + // That means we need one or two temporaries + qt_nameprep(&result, prevLen); + labelLength = result.length() - prevLen; + register int toReserve = labelLength + 4 + 6; // "xn--" plus some extra bytes + aceForm.resize(0); + if (toReserve > aceForm.capacity()) + aceForm.reserve(toReserve); + toPunycodeHelper(result.constData() + prevLen, result.size() - prevLen, &aceForm); + + // We use resize()+memcpy() here because we're overwriting the data we've copied + if (isIdnEnabled) { + QString tmp = QUrl::fromPunycode(aceForm.toLatin1()); + if (tmp.isEmpty()) + return QString(); // shouldn't happen, since we've just punycode-encoded it + result.resize(prevLen + tmp.size()); + memcpy(result.data() + prevLen, tmp.constData(), tmp.size() * sizeof(QChar)); + } else { + result.resize(prevLen + aceForm.size()); + memcpy(result.data() + prevLen, aceForm.constData(), aceForm.size() * sizeof(QChar)); + } + + if (!qt_check_std3rules(aceForm.constData(), aceForm.size())) + return QString(); + } + + + lastIdx = idx + 1; + if (lastIdx < domain.size() + 1) + result += QLatin1Char('.'); + else + break; + } + return result; +} + + +QUrlPrivate::QUrlPrivate() +{ + ref = 1; + port = -1; + isValid = false; + isHostValid = true; + parsingMode = QUrl::TolerantMode; + valueDelimiter = '='; + pairDelimiter = '&'; + stateFlags = 0; + hasFragment = false; + hasQuery = false; +} + +QUrlPrivate::QUrlPrivate(const QUrlPrivate ©) + : scheme(copy.scheme), + userName(copy.userName), + password(copy.password), + host(copy.host), + path(copy.path), + query(copy.query), + fragment(copy.fragment), + encodedOriginal(copy.encodedOriginal), + encodedUserName(copy.encodedUserName), + encodedPassword(copy.encodedPassword), + encodedPath(copy.encodedPath), + encodedFragment(copy.encodedFragment), + port(copy.port), + parsingMode(copy.parsingMode), + hasQuery(copy.hasQuery), + hasFragment(copy.hasFragment), + isValid(copy.isValid), + isHostValid(copy.isHostValid), + valueDelimiter(copy.valueDelimiter), + pairDelimiter(copy.pairDelimiter), + stateFlags(copy.stateFlags), + encodedNormalized(copy.encodedNormalized) +{ ref = 1; } + +QString QUrlPrivate::canonicalHost() const +{ + if (QURL_HASFLAG(stateFlags, HostCanonicalized) || host.isEmpty()) + return host; + + QUrlPrivate *that = const_cast<QUrlPrivate *>(this); + QURL_SETFLAG(that->stateFlags, HostCanonicalized); + if (host.contains(QLatin1Char(':'))) { + // This is an IP Literal, use _IPLiteral to validate + QByteArray ba = host.toLatin1(); + bool needsBraces = false; + if (!ba.startsWith('[')) { + // surround the IP Literal with [ ] if it's not already done so + ba.reserve(ba.length() + 2); + ba.prepend('['); + ba.append(']'); + needsBraces = true; + } + + const char *ptr = ba.constData(); + if (!_IPLiteral(&ptr)) + that->host.clear(); + else if (needsBraces) + that->host = QString::fromLatin1(ba.toLower()); + else + that->host = host.toLower(); + } else { + that->host = qt_ACE_do(host, NormalizeAce); + } + that->isHostValid = !that->host.isNull(); + return that->host; +} + +// From RFC 3896, Appendix A Collected ABNF for URI +// authority = [ userinfo "@" ] host [ ":" port ] +// userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) +// host = IP-literal / IPv4address / reg-name +// port = *DIGIT +//[...] +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// +// query = *( pchar / "/" / "?" ) +// +// fragment = *( pchar / "/" / "?" ) +// +// pct-encoded = "%" HEXDIG HEXDIG +// +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// reserved = gen-delims / sub-delims +// gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" +// / "*" / "+" / "," / ";" / "=" + +// use defines for concatenation: +#define ABNF_sub_delims "!$&'()*+,;=" +#define ABNF_gen_delims ":/?#[]@" +#define ABNF_pchar ABNF_sub_delims ":@" +#define ABNF_reserved ABNF_sub_delims ABNF_gen_delims + +// list the characters that don't have to be converted according to the list above. +// "unreserved" is already automatically not encoded, so we don't have to list it. +// the path component has a complex ABNF that basically boils down to +// slash-separated segments of "pchar" + +static const char userNameExcludeChars[] = ABNF_sub_delims; +static const char passwordExcludeChars[] = ABNF_sub_delims ":"; +static const char pathExcludeChars[] = ABNF_pchar "/"; +static const char queryExcludeChars[] = ABNF_pchar "/?"; +static const char fragmentExcludeChars[] = ABNF_pchar "/?"; + +void QUrlPrivate::ensureEncodedParts() const +{ + QUrlPrivate *that = const_cast<QUrlPrivate *>(this); + + if (encodedUserName.isNull()) + // userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + that->encodedUserName = toPercentEncodingHelper(userName, userNameExcludeChars); + if (encodedPassword.isNull()) + // userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + that->encodedPassword = toPercentEncodingHelper(password, passwordExcludeChars); + if (encodedPath.isNull()) + // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" ... also "/" + that->encodedPath = toPercentEncodingHelper(path, pathExcludeChars); + if (encodedFragment.isNull()) + // fragment = *( pchar / "/" / "?" ) + that->encodedFragment = toPercentEncodingHelper(fragment, fragmentExcludeChars); +} + +QString QUrlPrivate::authority(QUrl::FormattingOptions options) const +{ + if ((options & QUrl::RemoveAuthority) == QUrl::RemoveAuthority) + return QString(); + + QString tmp = userInfo(options); + if (!tmp.isEmpty()) + tmp += QLatin1Char('@'); + tmp += canonicalHost(); + if (!(options & QUrl::RemovePort) && port != -1) + tmp += QLatin1Char(':') + QString::number(port); + + return tmp; +} + +void QUrlPrivate::setAuthority(const QString &auth) +{ + isHostValid = true; + if (auth.isEmpty()) { + setUserInfo(QString()); + host.clear(); + port = -1; + return; + } + + // find the port section of the authority by searching from the + // end towards the beginning for numbers until a ':' is reached. + int portIndex = auth.length() - 1; + if (portIndex == 0) { + portIndex = -1; + } else { + short c = auth.at(portIndex--).unicode(); + if (c < '0' || c > '9') { + portIndex = -1; + } else while (portIndex >= 0) { + c = auth.at(portIndex).unicode(); + if (c == ':') { + break; + } else if (c == '.') { + portIndex = -1; + break; + } + --portIndex; + } + } + + if (portIndex != -1) { + port = 0; + for (int i = portIndex + 1; i < auth.length(); ++i) + port = (port * 10) + (auth.at(i).unicode() - '0'); + } else { + port = -1; + } + + int userInfoIndex = auth.indexOf(QLatin1Char('@')); + if (userInfoIndex != -1 && (portIndex == -1 || userInfoIndex < portIndex)) + setUserInfo(auth.left(userInfoIndex)); + + int hostIndex = 0; + if (userInfoIndex != -1) + hostIndex = userInfoIndex + 1; + int hostLength = auth.length() - hostIndex; + if (portIndex != -1) + hostLength -= (auth.length() - portIndex); + + host = auth.mid(hostIndex, hostLength).trimmed(); +} + +void QUrlPrivate::setUserInfo(const QString &userInfo) +{ + encodedUserName.clear(); + encodedPassword.clear(); + + int delimIndex = userInfo.indexOf(QLatin1Char(':')); + if (delimIndex == -1) { + userName = userInfo; + password.clear(); + return; + } + userName = userInfo.left(delimIndex); + password = userInfo.right(userInfo.length() - delimIndex - 1); +} + +void QUrlPrivate::setEncodedUserInfo(const QUrlParseData *parseData) +{ + userName.clear(); + password.clear(); + if (!parseData->userInfoLength) { + encodedUserName.clear(); + encodedPassword.clear(); + } else if (parseData->userInfoDelimIndex == -1) { + encodedUserName = QByteArray(parseData->userInfo, parseData->userInfoLength); + encodedPassword.clear(); + } else { + encodedUserName = QByteArray(parseData->userInfo, parseData->userInfoDelimIndex); + encodedPassword = QByteArray(parseData->userInfo + parseData->userInfoDelimIndex + 1, + parseData->userInfoLength - parseData->userInfoDelimIndex - 1); + } +} + +QString QUrlPrivate::userInfo(QUrl::FormattingOptions options) const +{ + if ((options & QUrl::RemoveUserInfo) == QUrl::RemoveUserInfo) + return QString(); + + QUrlPrivate *that = const_cast<QUrlPrivate *>(this); + if (userName.isNull()) + that->userName = fromPercentEncodingHelper(encodedUserName); + if (password.isNull()) + that->password = fromPercentEncodingHelper(encodedPassword); + + QString tmp = userName; + + if (!(options & QUrl::RemovePassword) && !password.isEmpty()) { + tmp += QLatin1Char(':'); + tmp += password; + } + + return tmp; +} + +/* + From http://www.ietf.org/rfc/rfc3986.txt, 5.2.3: Merge paths + + Returns a merge of the current path with the relative path passed + as argument. +*/ +QByteArray QUrlPrivate::mergePaths(const QByteArray &relativePath) const +{ + if (encodedPath.isNull()) + ensureEncodedParts(); + + // If the base URI has a defined authority component and an empty + // path, then return a string consisting of "/" concatenated with + // the reference's path; otherwise, + if (!authority().isEmpty() && encodedPath.isEmpty()) + return '/' + relativePath; + + // Return a string consisting of the reference's path component + // appended to all but the last segment of the base URI's path + // (i.e., excluding any characters after the right-most "/" in the + // base URI path, or excluding the entire base URI path if it does + // not contain any "/" characters). + QByteArray newPath; + if (!encodedPath.contains('/')) + newPath = relativePath; + else + newPath = encodedPath.left(encodedPath.lastIndexOf('/') + 1) + relativePath; + + return newPath; +} + +void QUrlPrivate::queryItem(int pos, int *value, int *end) +{ + *end = query.indexOf(pairDelimiter, pos); + if (*end == -1) + *end = query.size(); + *value = pos; + while (*value < *end) { + if (query[*value] == valueDelimiter) + break; + ++*value; + } +} + +/* + From http://www.ietf.org/rfc/rfc3986.txt, 5.2.4: Remove dot segments + + Removes unnecessary ../ and ./ from the path. Used for normalizing + the URL. +*/ +static void removeDotsFromPath(QByteArray *path) +{ + // The input buffer is initialized with the now-appended path + // components and the output buffer is initialized to the empty + // string. + char *out = path->data(); + const char *in = out; + const char *end = out + path->size(); + + // If the input buffer consists only of + // "." or "..", then remove that from the input + // buffer; + if (path->size() == 1 && in[0] == '.') + ++in; + else if (path->size() == 2 && in[0] == '.' && in[1] == '.') + in += 2; + // While the input buffer is not empty, loop: + while (in < end) { + + // otherwise, if the input buffer begins with a prefix of "../" or "./", + // then remove that prefix from the input buffer; + if (path->size() >= 2 && in[0] == '.' && in[1] == '/') + in += 2; + else if (path->size() >= 3 && in[0] == '.' && in[1] == '.' && in[2] == '/') + in += 3; + + // otherwise, if the input buffer begins with a prefix of + // "/./" or "/.", where "." is a complete path segment, + // then replace that prefix with "/" in the input buffer; + if (in <= end - 3 && in[0] == '/' && in[1] == '.' && in[2] == '/') { + in += 2; + continue; + } else if (in == end - 2 && in[0] == '/' && in[1] == '.') { + *out++ = '/'; + in += 2; + break; + } + + // otherwise, if the input buffer begins with a prefix + // of "/../" or "/..", where ".." is a complete path + // segment, then replace that prefix with "/" in the + // input buffer and remove the last //segment and its + // preceding "/" (if any) from the output buffer; + if (in <= end - 4 && in[0] == '/' && in[1] == '.' && in[2] == '.' && in[3] == '/') { + while (out > path->constData() && *(--out) != '/') + ; + if (out == path->constData() && *out != '/') + ++in; + in += 3; + continue; + } else if (in == end - 3 && in[0] == '/' && in[1] == '.' && in[2] == '.') { + while (out > path->constData() && *(--out) != '/') + ; + if (*out == '/') + ++out; + in += 3; + break; + } + + // otherwise move the first path segment in + // the input buffer to the end of the output + // buffer, including the initial "/" character + // (if any) and any subsequent characters up + // to, but not including, the next "/" + // character or the end of the input buffer. + *out++ = *in++; + while (in < end && *in != '/') + *out++ = *in++; + } + path->truncate(out - path->constData()); +} + +void QUrlPrivate::validate() const +{ + QUrlPrivate *that = (QUrlPrivate *)this; + that->encodedOriginal = that->toEncoded(); // may detach + parse(ParseOnly); + + QURL_SETFLAG(that->stateFlags, Validated); + + if (!isValid) + return; + + QString auth = authority(); // causes the non-encoded forms to be valid + + // authority() calls canonicalHost() which sets this + if (!isHostValid) + return; + + if (scheme == QLatin1String("mailto")) { + if (!host.isEmpty() || port != -1 || !userName.isEmpty() || !password.isEmpty()) { + that->isValid = false; + that->errorInfo.setParams(0, QT_TRANSLATE_NOOP(QUrl, "expected empty host, username," + "port and password"), + 0, 0); + } + } else if (scheme == QLatin1String("ftp") || scheme == QLatin1String("http")) { + if (host.isEmpty() && !(path.isEmpty() && encodedPath.isEmpty())) { + that->isValid = false; + that->errorInfo.setParams(0, QT_TRANSLATE_NOOP(QUrl, "the host is empty, but not the path"), + 0, 0); + } + } +} + +void QUrlPrivate::parse(ParseOptions parseOptions) const +{ + QUrlPrivate *that = (QUrlPrivate *)this; + that->errorInfo.setParams(0, 0, 0, 0); + if (encodedOriginal.isEmpty()) { + that->isValid = false; + that->errorInfo.setParams(0, QT_TRANSLATE_NOOP(QUrl, "empty"), + 0, 0); + QURL_SETFLAG(that->stateFlags, Validated | Parsed); + return; + } + + + QUrlParseData parseData; + memset(&parseData, 0, sizeof(parseData)); + parseData.userInfoDelimIndex = -1; + parseData.port = -1; + + const char *pptr = (char *) encodedOriginal.constData(); + const char **ptr = &pptr; + +#if defined (QURL_DEBUG) + qDebug("QUrlPrivate::parse(), parsing \"%s\"", pptr); +#endif + + // optional scheme + bool isSchemeValid = _scheme(ptr, &parseData); + + if (isSchemeValid == false) { + that->isValid = false; + char ch = *((*ptr)++); + that->errorInfo.setParams(*ptr, QT_TRANSLATE_NOOP(QUrl, "unexpected URL scheme"), + 0, ch); + QURL_SETFLAG(that->stateFlags, Validated | Parsed); +#if defined (QURL_DEBUG) + qDebug("QUrlPrivate::parse(), unrecognized: %c%s", ch, *ptr); +#endif + return; + } + + // hierpart + _hierPart(ptr, &parseData); + + // optional query + char ch = *((*ptr)++); + if (ch == '?') { + that->hasQuery = true; + _query(ptr, &parseData); + ch = *((*ptr)++); + } + + // optional fragment + if (ch == '#') { + that->hasFragment = true; + _fragment(ptr, &parseData); + } else if (ch != '\0') { + that->isValid = false; + that->errorInfo.setParams(*ptr, QT_TRANSLATE_NOOP(QUrl, "expected end of URL"), + 0, ch); + QURL_SETFLAG(that->stateFlags, Validated | Parsed); +#if defined (QURL_DEBUG) + qDebug("QUrlPrivate::parse(), unrecognized: %c%s", ch, *ptr); +#endif + return; + } + + // when doing lazy validation, this function is called after + // encodedOriginal has been constructed from the individual parts, + // only to see if the constructed URL can be parsed. in that case, + // parse() is called in ParseOnly mode; we don't want to set all + // the members over again. + if (parseOptions == ParseAndSet) { + QURL_UNSETFLAG(that->stateFlags, HostCanonicalized); + + if (parseData.scheme) { + QByteArray s(parseData.scheme, parseData.schemeLength); + that->scheme = fromPercentEncodingMutable(&s); + } + + that->setEncodedUserInfo(&parseData); + + QByteArray h(parseData.host, parseData.hostLength); + that->host = fromPercentEncodingMutable(&h); + that->port = parseData.port; + + that->path.clear(); + that->encodedPath = QByteArray(parseData.path, parseData.pathLength); + + if (that->hasQuery) + that->query = QByteArray(parseData.query, parseData.queryLength); + else + that->query.clear(); + + that->fragment.clear(); + if (that->hasFragment) { + that->encodedFragment = QByteArray(parseData.fragment, parseData.fragmentLength); + } else { + that->encodedFragment.clear(); + } + } + + that->isValid = true; + QURL_SETFLAG(that->stateFlags, Parsed); + +#if defined (QURL_DEBUG) + qDebug("QUrl::setUrl(), scheme = %s", that->scheme.toLatin1().constData()); + qDebug("QUrl::setUrl(), userInfo = %s", that->userInfo.toLatin1().constData()); + qDebug("QUrl::setUrl(), host = %s", that->host.toLatin1().constData()); + qDebug("QUrl::setUrl(), port = %i", that->port); + qDebug("QUrl::setUrl(), path = %s", fromPercentEncodingHelper(__path).toLatin1().constData()); + qDebug("QUrl::setUrl(), query = %s", __query.constData()); + qDebug("QUrl::setUrl(), fragment = %s", fromPercentEncodingHelper(__fragment).toLatin1().constData()); +#endif +} + +void QUrlPrivate::clear() +{ + scheme.clear(); + userName.clear(); + password.clear(); + host.clear(); + port = -1; + path.clear(); + query.clear(); + fragment.clear(); + + encodedOriginal.clear(); + encodedUserName.clear(); + encodedPassword.clear(); + encodedPath.clear(); + encodedFragment.clear(); + encodedNormalized.clear(); + + isValid = false; + hasQuery = false; + hasFragment = false; + + valueDelimiter = '='; + pairDelimiter = '&'; + + QURL_UNSETFLAG(stateFlags, Parsed | Validated | Normalized | HostCanonicalized); +} + +QByteArray QUrlPrivate::toEncoded(QUrl::FormattingOptions options) const +{ + if (!QURL_HASFLAG(stateFlags, Parsed)) parse(); + else ensureEncodedParts(); + + if (options==0x100) // private - see qHash(QUrl) + return normalized(); + + QByteArray url; + + if (!(options & QUrl::RemoveScheme) && !scheme.isEmpty()) { + url += scheme.toLatin1(); + url += ':'; + } + QString savedHost = host; // pre-validation, may be invalid! + QString auth = authority(); + bool doFileScheme = scheme == QLatin1String("file") && encodedPath.startsWith('/'); + if ((options & QUrl::RemoveAuthority) != QUrl::RemoveAuthority && (!auth.isEmpty() || doFileScheme || !savedHost.isEmpty())) { + if (doFileScheme && !encodedPath.startsWith('/')) + url += '/'; + url += "//"; + + if ((options & QUrl::RemoveUserInfo) != QUrl::RemoveUserInfo) { + bool hasUserOrPass = false; + if (!userName.isEmpty()) { + url += encodedUserName; + hasUserOrPass = true; + } + if (!(options & QUrl::RemovePassword) && !password.isEmpty()) { + url += ':'; + url += encodedPassword; + hasUserOrPass = true; + } + if (hasUserOrPass) + url += '@'; + } + + if (host.startsWith(QLatin1Char('['))) { + url += host.toLatin1(); + } else if (host.contains(QLatin1Char(':'))) { + url += '['; + url += host.toLatin1(); + url += ']'; + } else if (host.isEmpty() && !savedHost.isEmpty()) { + // this case is only possible with an invalid URL + // it's here only so that we can keep the original, invalid hostname + // in encodedOriginal. + // QUrl::isValid() will return false, so toEncoded() can be anything (it's not valid) + url += savedHost.toUtf8(); + } else { + url += QUrl::toAce(host); + } + if (!(options & QUrl::RemovePort) && port != -1) { + url += ':'; + url += QString::number(port).toAscii(); + } + } + + if (!(options & QUrl::RemovePath)) { + // check if we need to insert a slash + if (!encodedPath.isEmpty() && !auth.isEmpty()) { + if (!encodedPath.startsWith('/')) + url += '/'; + } + url += encodedPath; + + // check if we need to remove trailing slashes + while ((options & QUrl::StripTrailingSlash) && url.endsWith('/')) + url.chop(1); + } + + if (!(options & QUrl::RemoveQuery) && hasQuery) { + url += '?'; + url += query; + } + if (!(options & QUrl::RemoveFragment) && hasFragment) { + url += '#'; + url += encodedFragment; + } + + return url; +} + +#define qToLower(ch) (((ch|32) >= 'a' && (ch|32) <= 'z') ? (ch|32) : ch) + +const QByteArray &QUrlPrivate::normalized() const +{ + if (QURL_HASFLAG(stateFlags, QUrlPrivate::Normalized)) + return encodedNormalized; + + QUrlPrivate *that = const_cast<QUrlPrivate *>(this); + QURL_SETFLAG(that->stateFlags, QUrlPrivate::Normalized); + + QUrlPrivate tmp = *this; + tmp.scheme = tmp.scheme.toLower(); + tmp.host = tmp.canonicalHost(); + + // ensure the encoded and normalized parts of the URL + tmp.ensureEncodedParts(); + if (tmp.encodedUserName.contains('%')) + q_normalizePercentEncoding(&tmp.encodedUserName, userNameExcludeChars); + if (tmp.encodedPassword.contains('%')) + q_normalizePercentEncoding(&tmp.encodedPassword, passwordExcludeChars); + if (tmp.encodedFragment.contains('%')) + q_normalizePercentEncoding(&tmp.encodedFragment, fragmentExcludeChars); + + if (tmp.encodedPath.contains('%')) { + // the path is a bit special: + // the slashes shouldn't be encoded or decoded. + // They should remain exactly like they are right now + // + // treat the path as a slash-separated sequence of pchar + QByteArray result; + result.reserve(tmp.encodedPath.length()); + if (tmp.encodedPath.startsWith('/')) + result.append('/'); + + const char *data = tmp.encodedPath.constData(); + int lastSlash = 0; + int nextSlash; + do { + ++lastSlash; + nextSlash = tmp.encodedPath.indexOf('/', lastSlash); + int len; + if (nextSlash == -1) + len = tmp.encodedPath.length() - lastSlash; + else + len = nextSlash - lastSlash; + + if (memchr(data + lastSlash, '%', len)) { + // there's at least one percent before the next slash + QByteArray block = QByteArray(data + lastSlash, len); + q_normalizePercentEncoding(&block, pathExcludeChars); + result.append(block); + } else { + // no percents in this path segment, append wholesale + result.append(data + lastSlash, len); + } + + // append the slash too, if it's there + if (nextSlash != -1) + result.append('/'); + + lastSlash = nextSlash; + } while (lastSlash != -1); + + tmp.encodedPath = result; + } + + if (!tmp.scheme.isEmpty()) // relative test + removeDotsFromPath(&tmp.encodedPath); + + int qLen = tmp.query.length(); + for (int i = 0; i < qLen; i++) { + if (qLen - i > 2 && tmp.query.at(i) == '%') { + ++i; + tmp.query[i] = qToLower(tmp.query.at(i)); + ++i; + tmp.query[i] = qToLower(tmp.query.at(i)); + } + } + encodedNormalized = tmp.toEncoded(); + + return encodedNormalized; +} + +QString QUrlPrivate::createErrorString() +{ + if (isValid && isHostValid) + return QString(); + + QString errorString(QLatin1String(QT_TRANSLATE_NOOP(QUrl, "Invalid URL \""))); + errorString += QLatin1String(encodedOriginal.constData()); + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, "\"")); + + if (errorInfo._source) { + int position = encodedOriginal.indexOf(errorInfo._source) - 1; + if (position > 0) { + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ": error at position ")); + errorString += QString::number(position); + } else { + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ": ")); + errorString += QLatin1String(errorInfo._source); + } + } + + if (errorInfo._expected) { + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ": expected \'")); + errorString += QLatin1Char(errorInfo._expected); + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, "\'")); + } else { + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ": ")); + if (isHostValid) + errorString += QLatin1String(errorInfo._message); + else + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, "invalid hostname")); + } + if (errorInfo._found) { + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ", but found \'")); + errorString += QLatin1Char(errorInfo._found); + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, "\'")); + } + return errorString; +} + +/*! + \macro QT_NO_URL_CAST_FROM_STRING + \relates QUrl + + Disables automatic conversions from QString (or char *) to QUrl. + + Compiling your code with this define is useful when you have a lot of + code that uses QString for file names and you wish to convert it to + use QUrl for network transparency. In any code that uses QUrl, it can + help avoid missing QUrl::resolved() calls, and other misuses of + QString to QUrl conversions. + + \oldcode + url = filename; // probably not what you want + \newcode + url = QUrl::fromLocalFile(filename); + url = baseurl.resolved(QUrl(filename)); + \endcode + + \sa QT_NO_CAST_FROM_ASCII +*/ + + +/*! + Constructs a URL by parsing \a url. \a url is assumed to be in human + readable representation, with no percent encoding. QUrl will automatically + percent encode all characters that are not allowed in a URL. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qurl.cpp 0 + + To construct a URL from an encoded string, call fromEncoded(): + + \snippet doc/src/snippets/code/src_corelib_io_qurl.cpp 1 + + \sa setUrl(), setEncodedUrl(), fromEncoded(), TolerantMode +*/ +QUrl::QUrl(const QString &url) : d(0) +{ + if (!url.isEmpty()) + setUrl(url); +} + +/*! + \overload + + Parses the \a url using the parser mode \a parsingMode. + + \sa setUrl() +*/ +QUrl::QUrl(const QString &url, ParsingMode parsingMode) : d(0) +{ + if (!url.isEmpty()) + setUrl(url, parsingMode); + else { + d = new QUrlPrivate; + d->parsingMode = parsingMode; + } +} + +/*! + Constructs an empty QUrl object. +*/ +QUrl::QUrl() : d(0) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QUrl::QUrl(const QUrl &other) : d(other.d) +{ + if (d) + d->ref.ref(); +} + +/*! + Destructor; called immediately before the object is deleted. +*/ +QUrl::~QUrl() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + Returns true if the URL is valid; otherwise returns false. + + The URL is run through a conformance test. Every part of the URL + must conform to the standard encoding rules of the URI standard + for the URL to be reported as valid. + + \snippet doc/src/snippets/code/src_corelib_io_qurl.cpp 2 +*/ +bool QUrl::isValid() const +{ + if (!d) return false; + + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Validated)) d->validate(); + + return d->isValid && d->isHostValid; +} + +/*! + Returns true if the URL has no data; otherwise returns false. +*/ +bool QUrl::isEmpty() const +{ + if (!d) return true; + + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) + return d->encodedOriginal.isEmpty(); + else + return d->scheme.isEmpty() // no encodedScheme + && d->userName.isEmpty() && d->encodedUserName.isEmpty() + && d->password.isEmpty() && d->encodedPassword.isEmpty() + && d->host.isEmpty() // no encodedHost + && d->port == -1 + && d->path.isEmpty() && d->encodedPath.isEmpty() + && d->query.isEmpty() + && d->fragment.isEmpty() && d->encodedFragment.isEmpty(); +} + +/*! + Resets the content of the QUrl. After calling this function, the + QUrl is equal to one that has been constructed with the default + empty constructor. +*/ +void QUrl::clear() +{ + if (d && !d->ref.deref()) + delete d; + d = 0; +} + +/*! + Constructs a URL by parsing the contents of \a url. + + \a url is assumed to be in unicode format, with no percent + encoding. + + Calling isValid() will tell whether or not a valid URL was + constructed. + + \sa setEncodedUrl() +*/ +void QUrl::setUrl(const QString &url) +{ + setUrl(url, TolerantMode); +} + +/*! + \overload + + Parses \a url using the parsing mode \a parsingMode. + + \sa setEncodedUrl() +*/ +void QUrl::setUrl(const QString &url, ParsingMode parsingMode) +{ + detach(); + // escape all reserved characters and delimiters + // reserved = gen-delims / sub-delims + if (parsingMode != TolerantMode) { + setEncodedUrl(toPercentEncodingHelper(url, ABNF_reserved), parsingMode); + return; + } + + // Tolerant preprocessing + QString tmp = url; + + // Allow %20 in the QString variant + tmp.replace(QLatin1String("%20"), QLatin1String(" ")); + + // Percent-encode unsafe ASCII characters after host part + int start = tmp.indexOf(QLatin1String("//")); + if (start != -1) { + // Has host part, find delimiter + start += 2; // skip "//" + const char delims[] = "/#?"; + const char *d = delims; + int hostEnd = -1; + while (*d && (hostEnd = tmp.indexOf(QLatin1Char(*d), start)) == -1) + ++d; + start = (hostEnd == -1) ? -1 : hostEnd + 1; + } else { + start = 0; // Has no host part + } + QByteArray encodedUrl; + if (start != -1) { + QString hostPart = tmp.left(start); + QString otherPart = tmp.mid(start); + encodedUrl = toPercentEncodingHelper(hostPart, ":/?#[]@!$&'()*+,;=") + + toPercentEncodingHelper(otherPart, ":/?#@!$&'()*+,;="); + } else { + encodedUrl = toPercentEncodingHelper(tmp, ABNF_reserved); + } + setEncodedUrl(encodedUrl, StrictMode); +} + +/*! + Constructs a URL by parsing the contents of \a encodedUrl. + + \a encodedUrl is assumed to be a URL string in percent encoded + form, containing only ASCII characters. + + Use isValid() to determine if a valid URL was constructed. + + \sa setUrl() +*/ +void QUrl::setEncodedUrl(const QByteArray &encodedUrl) +{ + setEncodedUrl(encodedUrl, TolerantMode); +} + +inline static bool isHex(char c) +{ + c |= 0x20; + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); +} + +static inline char toHex(quint8 c) +{ + return c > 9 ? c - 10 + 'A' : c + '0'; +} + +/*! + Constructs a URL by parsing the contents of \a encodedUrl using + the given \a parsingMode. +*/ +void QUrl::setEncodedUrl(const QByteArray &encodedUrl, ParsingMode parsingMode) +{ + QByteArray tmp = encodedUrl; + if (!d) d = new QUrlPrivate; + else d->clear(); + if ((d->parsingMode = parsingMode) == TolerantMode) { + // Replace stray % with %25 + QByteArray copy = tmp; + for (int i = 0, j = 0; i < copy.size(); ++i, ++j) { + if (copy.at(i) == '%') { + if (i + 2 >= copy.size() || !isHex(copy.at(i + 1)) || !isHex(copy.at(i + 2))) { + tmp.replace(j, 1, "%25"); + j += 2; + } + } + } + + // Find the host part + int hostStart = tmp.indexOf("//"); + int hostEnd = -1; + if (hostStart != -1) { + // Has host part, find delimiter + hostStart += 2; // skip "//" + hostEnd = tmp.indexOf('/', hostStart); + if (hostEnd == -1) + hostEnd = tmp.indexOf('#', hostStart); + if (hostEnd == -1) + hostEnd = tmp.indexOf('?'); + if (hostEnd == -1) + hostEnd = tmp.length() - 1; + } + + // Reserved and unreserved characters are fine +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// reserved = gen-delims / sub-delims +// gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" +// / "*" / "+" / "," / ";" / "=" + // Replace everything else with percent encoding + static const char doEncode[] = " \"<>[\\]^`{|}"; + static const char doEncodeHost[] = " \"<>\\^`{|}"; + for (int i = 0; i < tmp.size(); ++i) { + quint8 c = quint8(tmp.at(i)); + if (c < 32 || c > 127 || + strchr(hostStart <= i && i <= hostEnd ? doEncodeHost : doEncode, c)) { + char buf[4]; + buf[0] = '%'; + buf[1] = toHex(c >> 4); + buf[2] = toHex(c & 0xf); + buf[3] = '\0'; + tmp.replace(i, 1, buf); + i += 2; + } + } + } + + d->encodedOriginal = tmp; +} + +/*! + Sets the scheme of the URL to \a scheme. As a scheme can only + contain ASCII characters, no conversion or encoding is done on the + input. + + The scheme describes the type (or protocol) of the URL. It's + represented by one or more ASCII characters at the start the URL, + and is followed by a ':'. The following example shows a URL where + the scheme is "ftp": + + \img qurl-authority2.png + + The scheme can also be empty, in which case the URL is interpreted + as relative. + + \sa scheme(), isRelative() +*/ +void QUrl::setScheme(const QString &scheme) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->scheme = scheme; +} + +/*! + Returns the scheme of the URL. If an empty string is returned, + this means the scheme is undefined and the URL is then relative. + + \sa setScheme(), isRelative() +*/ +QString QUrl::scheme() const +{ + if (!d) return QString(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + return d->scheme; +} + +/*! + Sets the authority of the URL to \a authority. + + The authority of a URL is the combination of user info, a host + name and a port. All of these elements are optional; an empty + authority is therefore valid. + + The user info and host are separated by a '@', and the host and + port are separated by a ':'. If the user info is empty, the '@' + must be omitted; although a stray ':' is permitted if the port is + empty. + + The following example shows a valid authority string: + + \img qurl-authority.png +*/ +void QUrl::setAuthority(const QString &authority) +{ + if (!d) d = new QUrlPrivate; + + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized | QUrlPrivate::HostCanonicalized); + d->setAuthority(authority); +} + +/*! + Returns the authority of the URL if it is defined; otherwise + an empty string is returned. + + \sa setAuthority() +*/ +QString QUrl::authority() const +{ + if (!d) return QString(); + + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + return d->authority(); +} + +/*! + Sets the user info of the URL to \a userInfo. The user info is an + optional part of the authority of the URL, as described in + setAuthority(). + + The user info consists of a user name and optionally a password, + separated by a ':'. If the password is empty, the colon must be + omitted. The following example shows a valid user info string: + + \img qurl-authority3.png + + \sa userInfo(), setUserName(), setPassword(), setAuthority() +*/ +void QUrl::setUserInfo(const QString &userInfo) +{ + if (!d) d = new QUrlPrivate; + + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->setUserInfo(userInfo.trimmed()); +} + +/*! + Returns the user info of the URL, or an empty string if the user + info is undefined. +*/ +QString QUrl::userInfo() const +{ + if (!d) return QString(); + + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + return d->userInfo(); +} + +/*! + Sets the URL's user name to \a userName. The \a userName is part + of the user info element in the authority of the URL, as described + in setUserInfo(). + + \sa setEncodedUserName(), userName(), setUserInfo() +*/ +void QUrl::setUserName(const QString &userName) +{ + if (!d) d = new QUrlPrivate; + + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->userName = userName; + d->encodedUserName.clear(); +} + +/*! + Returns the user name of the URL if it is defined; otherwise + an empty string is returned. + + \sa setUserName(), encodedUserName() +*/ +QString QUrl::userName() const +{ + if (!d) return QString(); + + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + d->userInfo(); // causes the unencoded form to be set + return d->userName; +} + +/*! + \since 4.4 + + Sets the URL's user name to the percent-encoded \a userName. The \a + userName is part of the user info element in the authority of the + URL, as described in setUserInfo(). + + Note: this function does not verify that \a userName is properly + encoded. It is the caller's responsibility to ensure that the any + delimiters (such as colons or slashes) are properly encoded. + + \sa setUserName(), encodedUserName(), setUserInfo() +*/ +void QUrl::setEncodedUserName(const QByteArray &userName) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->encodedUserName = userName; + d->userName.clear(); +} + +/*! + \since 4.4 + + Returns the user name of the URL if it is defined; otherwise + an empty string is returned. The returned value will have its + non-ASCII and other control characters percent-encoded, as in + toEncoded(). + + \sa setEncodedUserName() +*/ +QByteArray QUrl::encodedUserName() const +{ + if (!d) return QByteArray(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + d->ensureEncodedParts(); + return d->encodedUserName; +} + +/*! + Sets the URL's password to \a password. The \a password is part of + the user info element in the authority of the URL, as described in + setUserInfo(). + + \sa password(), setUserInfo() +*/ +void QUrl::setPassword(const QString &password) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->password = password; + d->encodedPassword.clear(); +} + +/*! + Returns the password of the URL if it is defined; otherwise + an empty string is returned. + + \sa setPassword() +*/ +QString QUrl::password() const +{ + if (!d) return QString(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + d->userInfo(); // causes the unencoded form to be set + return d->password; +} + +/*! + \since 4.4 + + Sets the URL's password to the percent-encoded \a password. The \a + password is part of the user info element in the authority of the + URL, as described in setUserInfo(). + + Note: this function does not verify that \a password is properly + encoded. It is the caller's responsibility to ensure that the any + delimiters (such as colons or slashes) are properly encoded. + + \sa setPassword(), encodedPassword(), setUserInfo() +*/ +void QUrl::setEncodedPassword(const QByteArray &password) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->encodedPassword = password; + d->password.clear(); +} + +/*! + \since 4.4 + + Returns the password of the URL if it is defined; otherwise an + empty string is returned. The returned value will have its + non-ASCII and other control characters percent-encoded, as in + toEncoded(). + + \sa setEncodedPassword(), toEncoded() +*/ +QByteArray QUrl::encodedPassword() const +{ + if (!d) return QByteArray(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + d->ensureEncodedParts(); + return d->encodedPassword; +} + +/*! + Sets the host of the URL to \a host. The host is part of the + authority. + + \sa host(), setAuthority() +*/ +void QUrl::setHost(const QString &host) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + d->isHostValid = true; + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized | QUrlPrivate::HostCanonicalized); + + d->host = host; +} + +/*! + Returns the host of the URL if it is defined; otherwise + an empty string is returned. +*/ +QString QUrl::host() const +{ + if (!d) return QString(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + if (d->host.isEmpty() || d->host.at(0) != QLatin1Char('[')) + return d->canonicalHost(); + QString tmp = d->host.mid(1); + tmp.truncate(tmp.length() - 1); + return tmp; +} + +/*! + \since 4.4 + + Sets the URL's host to the ACE- or percent-encoded \a host. The \a + host is part of the user info element in the authority of the + URL, as described in setAuthority(). + + \sa setHost(), encodedHost(), setAuthority(), fromAce() +*/ +void QUrl::setEncodedHost(const QByteArray &host) +{ + setHost(fromPercentEncodingHelper(host)); +} + +/*! + \since 4.4 + + Returns the host part of the URL if it is defined; otherwise + an empty string is returned. + + Note: encodedHost() does not return percent-encoded hostnames. Instead, + the ACE-encoded (bare ASCII in Punycode encoding) form will be + returned for any non-ASCII hostname. + + This function is equivalent to calling QUrl::toAce() on the return + value of host(). + + \sa setEncodedHost() +*/ +QByteArray QUrl::encodedHost() const +{ + // should we cache this in d->encodedHost? + return QUrl::toAce(host()); +} + +/*! + Sets the port of the URL to \a port. The port is part of the + authority of the URL, as described in setAuthority(). + + \a port must be between 0 and 65535 inclusive. Setting the + port to -1 indicates that the port is unspecified. +*/ +void QUrl::setPort(int port) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + if (port < -1 || port > 65535) { + qWarning("QUrl::setPort: Out of range"); + port = -1; + } + + d->port = port; +} + +/*! + Returns the port of the URL, or -1 if the port is unspecified. +*/ +int QUrl::port() const +{ + if (!d) return -1; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Validated)) d->validate(); + return d->port; +} + +/*! + \overload + \since 4.1 + + Returns the port of the URL, or \a defaultPort if the port is + unspecified. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qurl.cpp 3 +*/ +int QUrl::port(int defaultPort) const +{ + if (!d) return defaultPort; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + return d->port == -1 ? defaultPort : d->port; +} + +/*! + Sets the path of the URL to \a path. The path is the part of the + URL that comes after the authority but before the query string. + + \img qurl-ftppath.png + + For non-hierarchical schemes, the path will be everything + following the scheme declaration, as in the following example: + + \img qurl-mailtopath.png + + \sa path() +*/ +void QUrl::setPath(const QString &path) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->path = path; + d->encodedPath.clear(); +} + +/*! + Returns the path of the URL. + + \sa setPath() +*/ +QString QUrl::path() const +{ + if (!d) return QString(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + if (d->path.isNull()) { + QUrlPrivate *that = const_cast<QUrlPrivate *>(d); + that->path = fromPercentEncodingHelper(d->encodedPath); + } + return d->path; +} + +/*! + \since 4.4 + + Sets the URL's path to the percent-encoded \a path. The path is + the part of the URL that comes after the authority but before the + query string. + + \img qurl-ftppath.png + + For non-hierarchical schemes, the path will be everything + following the scheme declaration, as in the following example: + + \img qurl-mailtopath.png + + Note: this function does not verify that \a path is properly + encoded. It is the caller's responsibility to ensure that the any + delimiters (such as '?' and '#') are properly encoded. + + \sa setPath(), encodedPath(), setUserInfo() +*/ +void QUrl::setEncodedPath(const QByteArray &path) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->encodedPath = path; + d->path.clear(); +} + +/*! + \since 4.4 + + Returns the path of the URL if it is defined; otherwise an + empty string is returned. The returned value will have its + non-ASCII and other control characters percent-encoded, as in + toEncoded(). + + \sa setEncodedPath(), toEncoded() +*/ +QByteArray QUrl::encodedPath() const +{ + if (!d) return QByteArray(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + d->ensureEncodedParts(); + return d->encodedPath; +} + +/*! + \since 4.2 + + Returns true if this URL contains a Query (i.e., if ? was seen on it). + + \sa hasQueryItem(), encodedQuery() +*/ +bool QUrl::hasQuery() const +{ + if (!d) return false; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + return d->hasQuery; +} + +/*! + Sets the characters used for delimiting between keys and values, + and between key-value pairs in the URL's query string. The default + value delimiter is '=' and the default pair delimiter is '&'. + + \img qurl-querystring.png + + \a valueDelimiter will be used for separating keys from values, + and \a pairDelimiter will be used to separate key-value pairs. + Any occurrences of these delimiting characters in the encoded + representation of the keys and values of the query string are + percent encoded. + + If \a valueDelimiter is set to '-' and \a pairDelimiter is '/', + the above query string would instead be represented like this: + + \snippet doc/src/snippets/code/src_corelib_io_qurl.cpp 4 + + Calling this function does not change the delimiters of the + current query string. It only affects queryItems(), + setQueryItems() and addQueryItems(). +*/ +void QUrl::setQueryDelimiters(char valueDelimiter, char pairDelimiter) +{ + if (!d) d = new QUrlPrivate; + detach(); + + d->valueDelimiter = valueDelimiter; + d->pairDelimiter = pairDelimiter; +} + +/*! + Returns the character used to delimit between key-value pairs in + the query string of the URL. +*/ +char QUrl::queryPairDelimiter() const +{ + if (!d) return '&'; + return d->pairDelimiter; +} + +/*! + Returns the character used to delimit between keys and values in + the query string of the URL. +*/ +char QUrl::queryValueDelimiter() const +{ + if (!d) return '='; + return d->valueDelimiter; +} + +/*! + Sets the query string of the URL to \a query. The string is + inserted as-is, and no further encoding is performed when calling + toEncoded(). + + This function is useful if you need to pass a query string that + does not fit into the key-value pattern, or that uses a different + scheme for encoding special characters than what is suggested by + QUrl. + + Passing a value of QByteArray() to \a query (a null QByteArray) unsets + the query completely. However, passing a value of QByteArray("") + will set the query to an empty value, as if the original URL + had a lone "?". + + \sa encodedQuery(), hasQuery() +*/ +void QUrl::setEncodedQuery(const QByteArray &query) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->query = query; + d->hasQuery = !query.isNull(); +} + +/*! + Sets the query string of the URL to an encoded version of \a + query. The contents of \a query are converted to a string + internally, each pair delimited by the character returned by + pairDelimiter(), and the key and value are delimited by + valueDelimiter(). + + \note This method does not encode spaces (ASCII 0x20) as plus (+) signs, + like HTML forms do. If you need that kind of encoding, you must encode + the value yourself and use QUrl::setEncodedQueryItems. + + \sa setQueryDelimiters(), queryItems(), setEncodedQueryItems() +*/ +void QUrl::setQueryItems(const QList<QPair<QString, QString> > &query) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + + char alsoEncode[3]; + alsoEncode[0] = d->valueDelimiter; + alsoEncode[1] = d->pairDelimiter; + alsoEncode[2] = 0; + + QByteArray queryTmp; + for (int i = 0; i < query.size(); i++) { + if (i) queryTmp += d->pairDelimiter; + // query = *( pchar / "/" / "?" ) + queryTmp += toPercentEncodingHelper(query.at(i).first, queryExcludeChars, alsoEncode); + queryTmp += d->valueDelimiter; + // query = *( pchar / "/" / "?" ) + queryTmp += toPercentEncodingHelper(query.at(i).second, queryExcludeChars, alsoEncode); + } + + d->query = queryTmp; + d->hasQuery = !query.isEmpty(); +} + +/*! + \since 4.4 + + Sets the query string of the URL to the encoded version of \a + query. The contents of \a query are converted to a string + internally, each pair delimited by the character returned by + pairDelimiter(), and the key and value are delimited by + valueDelimiter(). + + Note: this function does not verify that the key-value pairs + are properly encoded. It is the caller's responsibility to ensure + that the query delimiters are properly encoded, if any. + + \sa setQueryDelimiters(), encodedQueryItems(), setQueryItems() +*/ +void QUrl::setEncodedQueryItems(const QList<QPair<QByteArray, QByteArray> > &query) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + + QByteArray queryTmp; + for (int i = 0; i < query.size(); i++) { + if (i) queryTmp += d->pairDelimiter; + queryTmp += query.at(i).first; + queryTmp += d->valueDelimiter; + queryTmp += query.at(i).second; + } + + d->query = queryTmp; + d->hasQuery = !query.isEmpty(); +} + +/*! + Inserts the pair \a key = \a value into the query string of the + URL. + + The key/value pair is encoded before it is added to the query. The + pair is converted into separate strings internally. The \a key and + \a value is first encoded into UTF-8 and then delimited by the + character returned by valueDelimiter(). Each key/value pair is + delimited by the character returned by pairDelimiter(). + + \note This method does not encode spaces (ASCII 0x20) as plus (+) signs, + like HTML forms do. If you need that kind of encoding, you must encode + the value yourself and use QUrl::addEncodedQueryItem. + + \sa addEncodedQueryItem() +*/ +void QUrl::addQueryItem(const QString &key, const QString &value) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + + char alsoEncode[3]; + alsoEncode[0] = d->valueDelimiter; + alsoEncode[1] = d->pairDelimiter; + alsoEncode[2] = 0; + + if (!d->query.isEmpty()) + d->query += d->pairDelimiter; + + // query = *( pchar / "/" / "?" ) + d->query += toPercentEncodingHelper(key, queryExcludeChars, alsoEncode); + d->query += d->valueDelimiter; + // query = *( pchar / "/" / "?" ) + d->query += toPercentEncodingHelper(value, queryExcludeChars, alsoEncode); + + d->hasQuery = !d->query.isEmpty(); +} + +/*! + \since 4.4 + + Inserts the pair \a key = \a value into the query string of the + URL. + + Note: this function does not verify that either \a key or \a value + are properly encoded. It is the caller's responsibility to ensure + that the query delimiters are properly encoded, if any. + + \sa addQueryItem(), setQueryDelimiters() +*/ +void QUrl::addEncodedQueryItem(const QByteArray &key, const QByteArray &value) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + + if (!d->query.isEmpty()) + d->query += d->pairDelimiter; + + d->query += key; + d->query += d->valueDelimiter; + d->query += value; + + d->hasQuery = !d->query.isEmpty(); +} + +/*! + Returns the query string of the URL, as a map of keys and values. + + \note This method does not decode spaces plus (+) signs as spaces (ASCII + 0x20), like HTML forms do. If you need that kind of decoding, you must + use QUrl::encodedQueryItems and decode the data yourself. + + \sa setQueryItems(), setEncodedQuery() +*/ +QList<QPair<QString, QString> > QUrl::queryItems() const +{ + if (!d) return QList<QPair<QString, QString> >(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + QList<QPair<QString, QString> > itemMap; + + int pos = 0; + const char *query = d->query.constData(); + while (pos < d->query.size()) { + int valuedelim, end; + d->queryItem(pos, &valuedelim, &end); + QByteArray q(query + pos, valuedelim - pos); + if (valuedelim < end) { + QByteArray v(query + valuedelim + 1, end - valuedelim - 1); + itemMap += qMakePair(fromPercentEncodingMutable(&q), + fromPercentEncodingMutable(&v)); + } else { + itemMap += qMakePair(fromPercentEncodingMutable(&q), QString()); + } + pos = end + 1; + } + + return itemMap; +} + +/*! + \since 4.4 + + Returns the query string of the URL, as a map of encoded keys and values. + + \sa setEncodedQueryItems(), setQueryItems(), setEncodedQuery() +*/ +QList<QPair<QByteArray, QByteArray> > QUrl::encodedQueryItems() const +{ + if (!d) return QList<QPair<QByteArray, QByteArray> >(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + QList<QPair<QByteArray, QByteArray> > itemMap; + + int pos = 0; + const char *query = d->query.constData(); + while (pos < d->query.size()) { + int valuedelim, end; + d->queryItem(pos, &valuedelim, &end); + if (valuedelim < end) + itemMap += qMakePair(QByteArray(query + pos, valuedelim - pos), + QByteArray(query + valuedelim + 1, end - valuedelim - 1)); + else + itemMap += qMakePair(QByteArray(query + pos, valuedelim - pos), QByteArray()); + pos = end + 1; + } + + return itemMap; +} + +/*! + Returns true if there is a query string pair whose key is equal + to \a key from the URL. + + \sa hasEncodedQueryItem() +*/ +bool QUrl::hasQueryItem(const QString &key) const +{ + if (!d) return false; + return hasEncodedQueryItem(toPercentEncoding(key, queryExcludeChars)); +} + +/*! + \since 4.4 + + Returns true if there is a query string pair whose key is equal + to \a key from the URL. + + Note: if the encoded \a key does not match the encoded version of + the query, this function will return false. That is, if the + encoded query of this URL is "search=Qt%20Rules", calling this + function with \a key = "%73earch" will return false. + + \sa hasQueryItem() +*/ +bool QUrl::hasEncodedQueryItem(const QByteArray &key) const +{ + if (!d) return false; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + int pos = 0; + const char *query = d->query.constData(); + while (pos < d->query.size()) { + int valuedelim, end; + d->queryItem(pos, &valuedelim, &end); + if (key == QByteArray::fromRawData(query + pos, valuedelim - pos)) + return true; + pos = end + 1; + } + return false; +} + +/*! + Returns the first query string value whose key is equal to \a key + from the URL. + + \note This method does not decode spaces plus (+) signs as spaces (ASCII + 0x20), like HTML forms do. If you need that kind of decoding, you must + use QUrl::encodedQueryItemValue and decode the data yourself. + + \sa allQueryItemValues() +*/ +QString QUrl::queryItemValue(const QString &key) const +{ + if (!d) return QString(); + QByteArray tmp = encodedQueryItemValue(toPercentEncoding(key, queryExcludeChars)); + return fromPercentEncodingMutable(&tmp); +} + +/*! + \since 4.4 + + Returns the first query string value whose key is equal to \a key + from the URL. + + Note: if the encoded \a key does not match the encoded version of + the query, this function will not work. That is, if the + encoded query of this URL is "search=Qt%20Rules", calling this + function with \a key = "%73earch" will return an empty string. + + \sa queryItemValue(), allQueryItemValues() +*/ +QByteArray QUrl::encodedQueryItemValue(const QByteArray &key) const +{ + if (!d) return QByteArray(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + int pos = 0; + const char *query = d->query.constData(); + while (pos < d->query.size()) { + int valuedelim, end; + d->queryItem(pos, &valuedelim, &end); + if (key == QByteArray::fromRawData(query + pos, valuedelim - pos)) + return valuedelim < end ? + QByteArray(query + valuedelim + 1, end - valuedelim - 1) : QByteArray(); + pos = end + 1; + } + return QByteArray(); +} + +/*! + Returns the a list of query string values whose key is equal to + \a key from the URL. + + \note This method does not decode spaces plus (+) signs as spaces (ASCII + 0x20), like HTML forms do. If you need that kind of decoding, you must + use QUrl::allEncodedQueryItemValues and decode the data yourself. + + \sa queryItemValue() +*/ +QStringList QUrl::allQueryItemValues(const QString &key) const +{ + if (!d) return QStringList(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + QByteArray encodedKey = toPercentEncoding(key, queryExcludeChars); + QStringList values; + + int pos = 0; + const char *query = d->query.constData(); + while (pos < d->query.size()) { + int valuedelim, end; + d->queryItem(pos, &valuedelim, &end); + if (encodedKey == QByteArray::fromRawData(query + pos, valuedelim - pos)) { + QByteArray v(query + valuedelim + 1, end - valuedelim - 1); + values += valuedelim < end ? + fromPercentEncodingMutable(&v) + : QString(); + } + pos = end + 1; + } + + return values; +} + +/*! + \since 4.4 + + Returns the a list of query string values whose key is equal to + \a key from the URL. + + Note: if the encoded \a key does not match the encoded version of + the query, this function will not work. That is, if the + encoded query of this URL is "search=Qt%20Rules", calling this + function with \a key = "%73earch" will return an empty list. + + \sa allQueryItemValues(), queryItemValue(), encodedQueryItemValue() +*/ +QList<QByteArray> QUrl::allEncodedQueryItemValues(const QByteArray &key) const +{ + if (!d) return QList<QByteArray>(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + QList<QByteArray> values; + + int pos = 0; + const char *query = d->query.constData(); + while (pos < d->query.size()) { + int valuedelim, end; + d->queryItem(pos, &valuedelim, &end); + if (key == QByteArray::fromRawData(query + pos, valuedelim - pos)) + values += valuedelim < end ? + QByteArray(query + valuedelim + 1, end - valuedelim - 1) + : QByteArray(); + pos = end + 1; + } + + return values; +} + +/*! + Removes the first query string pair whose key is equal to \a key + from the URL. + + \sa removeAllQueryItems() +*/ +void QUrl::removeQueryItem(const QString &key) +{ + if (!d) return; + removeEncodedQueryItem(toPercentEncoding(key, queryExcludeChars)); +} + +/*! + \since 4.4 + + Removes the first query string pair whose key is equal to \a key + from the URL. + + Note: if the encoded \a key does not match the encoded version of + the query, this function will not work. That is, if the + encoded query of this URL is "search=Qt%20Rules", calling this + function with \a key = "%73earch" will do nothing. + + \sa removeQueryItem(), removeAllQueryItems() +*/ +void QUrl::removeEncodedQueryItem(const QByteArray &key) +{ + if (!d) return; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + + int pos = 0; + const char *query = d->query.constData(); + while (pos < d->query.size()) { + int valuedelim, end; + d->queryItem(pos, &valuedelim, &end); + if (key == QByteArray::fromRawData(query + pos, valuedelim - pos)) { + if (end < d->query.size()) + ++end; // remove additional '%' + d->query.remove(pos, end - pos); + return; + } + pos = end + 1; + } +} + +/*! + Removes all the query string pairs whose key is equal to \a key + from the URL. + + \sa removeQueryItem() +*/ +void QUrl::removeAllQueryItems(const QString &key) +{ + if (!d) return; + removeAllEncodedQueryItems(toPercentEncoding(key, queryExcludeChars)); +} + +/*! + \since 4.4 + + Removes all the query string pairs whose key is equal to \a key + from the URL. + + Note: if the encoded \a key does not match the encoded version of + the query, this function will not work. That is, if the + encoded query of this URL is "search=Qt%20Rules", calling this + function with \a key = "%73earch" will do nothing. + + \sa removeQueryItem() +*/ +void QUrl::removeAllEncodedQueryItems(const QByteArray &key) +{ + if (!d) return; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + + int pos = 0; + const char *query = d->query.constData(); + while (pos < d->query.size()) { + int valuedelim, end; + d->queryItem(pos, &valuedelim, &end); + if (key == QByteArray::fromRawData(query + pos, valuedelim - pos)) { + if (end < d->query.size()) + ++end; // remove additional '%' + d->query.remove(pos, end - pos); + } else { + pos = end + 1; + } + } +} + +/*! + Returns the query string of the URL in percent encoded form. +*/ +QByteArray QUrl::encodedQuery() const +{ + if (!d) return QByteArray(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + return d->query; +} + +/*! + Sets the fragment of the URL to \a fragment. The fragment is the + last part of the URL, represented by a '#' followed by a string of + characters. It is typically used in HTTP for referring to a + certain link or point on a page: + + \img qurl-fragment.png + + The fragment is sometimes also referred to as the URL "reference". + + Passing an argument of QString() (a null QString) will unset the fragment. + Passing an argument of QString("") (an empty but not null QString) + will set the fragment to an empty string (as if the original URL + had a lone "#"). + + \sa fragment(), hasFragment() +*/ +void QUrl::setFragment(const QString &fragment) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->fragment = fragment; + d->hasFragment = !fragment.isNull(); + d->encodedFragment.clear(); +} + +/*! + Returns the fragment of the URL. + + \sa setFragment() +*/ +QString QUrl::fragment() const +{ + if (!d) return QString(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + if (d->fragment.isNull() && !d->encodedFragment.isNull()) { + QUrlPrivate *that = const_cast<QUrlPrivate *>(d); + that->fragment = fromPercentEncodingHelper(d->encodedFragment); + } + return d->fragment; +} + +/*! + \since 4.4 + + Sets the URL's fragment to the percent-encoded \a fragment. The fragment is the + last part of the URL, represented by a '#' followed by a string of + characters. It is typically used in HTTP for referring to a + certain link or point on a page: + + \img qurl-fragment.png + + The fragment is sometimes also referred to as the URL "reference". + + Passing an argument of QByteArray() (a null QByteArray) will unset + the fragment. Passing an argument of QByteArray("") (an empty but + not null QByteArray) will set the fragment to an empty string (as + if the original URL had a lone "#"). + + \sa setFragment(), encodedFragment() +*/ +void QUrl::setEncodedFragment(const QByteArray &fragment) +{ + if (!d) d = new QUrlPrivate; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + d->encodedFragment = fragment; + d->hasFragment = !fragment.isNull(); + d->fragment.clear(); +} + +/*! + \since 4.4 + + Returns the fragment of the URL if it is defined; otherwise an + empty string is returned. The returned value will have its + non-ASCII and other control characters percent-encoded, as in + toEncoded(). + + \sa setEncodedFragment(), toEncoded() +*/ +QByteArray QUrl::encodedFragment() const +{ + if (!d) return QByteArray(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + d->ensureEncodedParts(); + return d->encodedFragment; +} + +/*! + \since 4.2 + + Returns true if this URL contains a fragment (i.e., if # was seen on it). + + \sa fragment(), setFragment() +*/ +bool QUrl::hasFragment() const +{ + if (!d) return false; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + return d->hasFragment; +} + +/*! + Returns the result of the merge of this URL with \a relative. This + URL is used as a base to convert \a relative to an absolute URL. + + If \a relative is not a relative URL, this function will return \a + relative directly. Otherwise, the paths of the two URLs are + merged, and the new URL returned has the scheme and authority of + the base URL, but with the merged path, as in the following + example: + + \snippet doc/src/snippets/code/src_corelib_io_qurl.cpp 5 + + Calling resolved() with ".." returns a QUrl whose directory is + one level higher than the original. Similarly, calling resolved() + with "../.." removes two levels from the path. If \a relative is + "/", the path becomes "/". + + \sa isRelative() +*/ +QUrl QUrl::resolved(const QUrl &relative) const +{ + if (!d) return relative; + if (!relative.d) return *this; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + if (!QURL_HASFLAG(relative.d->stateFlags, QUrlPrivate::Parsed)) + relative.d->parse(); + + d->ensureEncodedParts(); + relative.d->ensureEncodedParts(); + + QUrl t; + // be non strict and allow scheme in relative url + if (!relative.d->scheme.isEmpty() && relative.d->scheme != d->scheme) { + t = relative; + } else { + if (!relative.authority().isEmpty()) { + t = relative; + } else { + t.d = new QUrlPrivate; + if (relative.d->encodedPath.isEmpty()) { + t.d->encodedPath = d->encodedPath; + t.setEncodedQuery(relative.d->hasQuery ? relative.d->query : d->query); + } else { + t.d->encodedPath = relative.d->encodedPath.at(0) == '/' + ? relative.d->encodedPath + : d->mergePaths(relative.d->encodedPath); + t.setEncodedQuery(relative.d->query); + } + t.d->encodedUserName = d->encodedUserName; + t.d->encodedPassword = d->encodedPassword; + t.d->host = d->host; + t.d->port = d->port; + } + t.setScheme(d->scheme); + } + t.setFragment(relative.fragment()); + removeDotsFromPath(&t.d->encodedPath); + t.d->path.clear(); + +#if defined(QURL_DEBUG) + qDebug("QUrl(\"%s\").resolved(\"%s\") = \"%s\"", + toEncoded().constData(), + relative.toEncoded().constData(), + t.toEncoded().constData()); +#endif + return t; +} + +/*! + Returns true if the URL is relative; otherwise returns false. A + URL is relative if its scheme is undefined; this function is + therefore equivalent to calling scheme().isEmpty(). +*/ +bool QUrl::isRelative() const +{ + if (!d) return true; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + return d->scheme.isEmpty(); +} + +/*! + Returns the human-displayable string representation of the + URL. The output can be customized by passing flags with \a + options. + + \sa FormattingOptions, toEncoded() +*/ +QString QUrl::toString(FormattingOptions options) const +{ + if (!d) return QString(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + QString url; + + if (!(options & QUrl::RemoveScheme) && !d->scheme.isEmpty()) + url += d->scheme + QLatin1Char(':'); + QString ourPath = path(); + if ((options & QUrl::RemoveAuthority) != QUrl::RemoveAuthority) { + bool doFileScheme = d->scheme == QLatin1String("file") && ourPath.startsWith(QLatin1Char('/')); + QString tmp = d->authority(options); + if (!tmp.isNull() || doFileScheme) { + if (doFileScheme && !ourPath.startsWith(QLatin1Char('/'))) + url += QLatin1Char('/'); + url += QLatin1String("//"); + url += tmp; + } + } + if (!(options & QUrl::RemovePath)) { + // check if we need to insert a slash + if ((options & QUrl::RemoveAuthority) != QUrl::RemoveAuthority + && !d->authority(options).isEmpty() && !ourPath.isEmpty() && ourPath.at(0) != QLatin1Char('/')) + url += QLatin1Char('/'); + url += ourPath; + // check if we need to remove trailing slashes + while ((options & StripTrailingSlash) && url.endsWith(QLatin1Char('/'))) + url.chop(1); + } + + if (!(options & QUrl::RemoveQuery) && d->hasQuery) { + url += QLatin1Char('?'); + url += fromPercentEncoding(d->query); + } + if (!(options & QUrl::RemoveFragment) && d->hasFragment) { + url += QLatin1Char('#'); + url += fragment(); + } + + return url; +} + +/*! + Returns the encoded representation of the URL if it's valid; + otherwise an empty QByteArray is returned. The output can be + customized by passing flags with \a options. + + The user info, path and fragment are all converted to UTF-8, and + all non-ASCII characters are then percent encoded. The host name + is encoded using Punycode. +*/ +QByteArray QUrl::toEncoded(FormattingOptions options) const +{ + if (!d) return QByteArray(); + return d->toEncoded(options); +} + +/*! + Parses \a input and returns the corresponding QUrl. \a input is + assumed to be in encoded form, containing only ASCII characters. + + The URL is parsed using TolerantMode. + + \sa toEncoded(), setUrl() +*/ +QUrl QUrl::fromEncoded(const QByteArray &input) +{ + QUrl tmp; + tmp.setEncodedUrl(input, TolerantMode); + return tmp; +} + +/*! + \overload + + Parses the URL using \a parsingMode. + + \sa toEncoded(), setUrl() +*/ +QUrl QUrl::fromEncoded(const QByteArray &input, ParsingMode parsingMode) +{ + QUrl tmp; + tmp.setEncodedUrl(input, parsingMode); + return tmp; +} + +/*! + Returns a decoded copy of \a input. \a input is first decoded from + percent encoding, then converted from UTF-8 to unicode. +*/ +QString QUrl::fromPercentEncoding(const QByteArray &input) +{ + return fromPercentEncodingHelper(input); +} + +/*! + Returns an encoded copy of \a input. \a input is first converted + to UTF-8, and all ASCII-characters that are not in the unreserved group + are percent encoded. To prevent characters from being percent encoded + pass them to \a exclude. To force characters to be percent encoded pass + them to \a include. + + Unreserved is defined as: + ALPHA / DIGIT / "-" / "." / "_" / "~" + + \snippet doc/src/snippets/code/src_corelib_io_qurl.cpp 6 +*/ +QByteArray QUrl::toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include) +{ + return toPercentEncodingHelper(input, exclude.constData(), include.constData()); +} + +/*! + \obsolete + Returns a \a uc in Punycode encoding. + + Punycode is a Unicode encoding used for internationalized domain + names, as defined in RFC3492. If you want to convert a domain name from + Unicode to its ASCII-compatible representation, use toAce(). +*/ +QByteArray QUrl::toPunycode(const QString &uc) +{ + QString output; + toPunycodeHelper(uc.constData(), uc.size(), &output); + return output.toLatin1(); +} + +/*! + \obsolete + Returns the Punycode decoded representation of \a pc. + + Punycode is a Unicode encoding used for internationalized domain + names, as defined in RFC3492. If you want to convert a domain from + its ASCII-compatible encoding to the Unicode representation, use + fromAce(). +*/ +QString QUrl::fromPunycode(const QByteArray &pc) +{ + uint n = initial_n; + uint i = 0; + uint bias = initial_bias; + + // strip any ACE prefix + int start = pc.startsWith("xn--") ? 4 : 0; + if (!start) + return QString::fromLatin1(pc); + + // find the last delimiter character '-' in the input array. copy + // all data before this delimiter directly to the output array. + int delimiterPos = pc.lastIndexOf(0x2d); + QString output = delimiterPos < 4 ? + QString() : QString::fromLatin1(pc.constData() + start, delimiterPos - start); + + // if a delimiter was found, skip to the position after it; + // otherwise start at the front of the input string. everything + // before the delimiter is assumed to be basic code points. + uint cnt = delimiterPos + 1; + + // loop through the rest of the input string, inserting non-basic + // characters into output as we go. + while (cnt < (uint) pc.size()) { + uint oldi = i; + uint w = 1; + + // find the next index for inserting a non-basic character. + for (uint k = base; cnt < (uint) pc.size(); k += base) { + // grab a character from the punycode input and find its + // delta digit (each digit code is part of the + // variable-length integer delta) + uint digit = pc.at(cnt++); + if (digit - 48 < 10) digit -= 22; + else if (digit - 65 < 26) digit -= 65; + else if (digit - 97 < 26) digit -= 97; + else digit = base; + + // reject out of range digits + if (digit >= base || digit > (Q_MAXINT - i) / w) + return QLatin1String(""); + + i += (digit * w); + + // detect threshold to stop reading delta digits + uint t; + if (k <= bias) t = tmin; + else if (k >= bias + tmax) t = tmax; + else t = k - bias; + if (digit < t) break; + + w *= (base - t); + } + + // find new bias and calculate the next non-basic code + // character. + bias = adapt(i - oldi, output.length() + 1, oldi == 0); + n += i / (output.length() + 1); + + // allow the deltas to wrap around + i %= (output.length() + 1); + + // insert the character n at position i + output.insert((uint) i, QChar((ushort) n)); + ++i; + } + + return output; +} + +/*! + \since 4.2 + + Returns the Unicode form of the given domain name + \a domain, which is encoded in the ASCII Compatible Encoding (ACE). + The result of this function is considered equivalent to \a domain. + + If the value in \a domain cannot be encoded, it will be converted + to QString and returned. + + The ASCII Compatible Encoding (ACE) is defined by RFC 3490, RFC 3491 + and RFC 3492. It is part of the Internationalizing Domain Names in + Applications (IDNA) specification, which allows for domain names + (like \c "example.com") to be written using international + characters. +*/ +QString QUrl::fromAce(const QByteArray &domain) +{ + return qt_ACE_do(QString::fromLatin1(domain), NormalizeAce); +} + +/*! + \since 4.2 + + Returns the ASCII Compatible Encoding of the given domain name \a domain. + The result of this function is considered equivalent to \a domain. + + The ASCII-Compatible Encoding (ACE) is defined by RFC 3490, RFC 3491 + and RFC 3492. It is part of the Internationalizing Domain Names in + Applications (IDNA) specification, which allows for domain names + (like \c "example.com") to be written using international + characters. + + This function return an empty QByteArra if \a domain is not a valid + hostname. Note, in particular, that IPv6 literals are not valid domain + names. +*/ +QByteArray QUrl::toAce(const QString &domain) +{ + QString result = qt_ACE_do(domain, ToAceOnly); + return result.toLatin1(); +} + +/*! + \since 4.2 + + Returns the current whitelist of top-level domains that are allowed + to have non-ASCII characters in their compositions. + + See setIdnWhitelist() for the rationale of this list. +*/ +QStringList QUrl::idnWhitelist() +{ + if (user_idn_whitelist) + return *user_idn_whitelist; + QStringList list; + unsigned int i = 0; + while (i < sizeof(idn_whitelist)/sizeof(const char *)) { + list << QLatin1String(idn_whitelist[i]); + ++i; + } + return list; +} + +/*! + \since 4.2 + + Sets the whitelist of Top-Level Domains (TLDs) that are allowed to have + non-ASCII characters in domains to the value of \a list. + + Qt has comes a default list that contains the Internet top-level domains + that have published support for Internationalized Domain Names (IDNs) + and rules to guarantee that no deception can happen between similarly-looking + characters (such as the Latin lowercase letter \c 'a' and the Cyrillic + equivalent, which in most fonts are visually identical). + + This list is periodically maintained, as registrars publish new rules. + + This function is provided for those who need to manipulate the list, in + order to add or remove a TLD. It is not recommended to change its value + for purposes other than testing, as it may expose users to security risks. +*/ +void QUrl::setIdnWhitelist(const QStringList &list) +{ + if (!user_idn_whitelist) + user_idn_whitelist = new QStringList; + *user_idn_whitelist = list; +} + +/*! + \internal + + Returns true if this URL is "less than" the given \a url. This + provides a means of ordering URLs. +*/ +bool QUrl::operator <(const QUrl &url) const +{ + if (!d) return url.d ? QByteArray() < url.d->normalized() : false; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + if (!url.d) return d->normalized() < QByteArray(); + if (!QURL_HASFLAG(url.d->stateFlags, QUrlPrivate::Parsed)) url.d->parse(); + return d->normalized() < url.d->normalized(); +} + +/*! + Returns true if this URL and the given \a url are equal; + otherwise returns false. +*/ +bool QUrl::operator ==(const QUrl &url) const +{ + if (!d) return url.isEmpty(); + if (!url.d) return isEmpty(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + if (!QURL_HASFLAG(url.d->stateFlags, QUrlPrivate::Parsed)) url.d->parse(); + return d->normalized() == url.d->normalized(); +} + +/*! + Returns true if this URL and the given \a url are not equal; + otherwise returns false. +*/ +bool QUrl::operator !=(const QUrl &url) const +{ + return !(*this == url); +} + +/*! + Assigns the specified \a url to this object. +*/ +QUrl &QUrl::operator =(const QUrl &url) +{ + if (!d) { + if (url.d) { + url.d->ref.ref(); + d = url.d; + } + } else { + if (url.d) + qAtomicAssign(d, url.d); + else + clear(); + } + return *this; +} + +/*! + Assigns the specified \a url to this object. +*/ +QUrl &QUrl::operator =(const QString &url) +{ + if (url.isEmpty()) { + clear(); + } else { + QUrl tmp(url); + if (!d) d = new QUrlPrivate; + qAtomicAssign(d, tmp.d); + } + return *this; +} + +/*! + \fn void QUrl::swap(QUrl &other) + \since 4.8 + + Swaps URL \a other with this URL. This operation is very + fast and never fails. +*/ + +/*! \internal + + Forces a detach. +*/ +void QUrl::detach() +{ + if (!d) + d = new QUrlPrivate; + else + qAtomicDetach(d); +} + +/*! + \internal +*/ +bool QUrl::isDetached() const +{ + return !d || d->ref == 1; +} + + +/*! + Returns a QUrl representation of \a localFile, interpreted as a local + file. This function accepts paths separated by slashes as well as the + native separator for this platform. + + This function also accepts paths with a doubled leading slash (or + backslash) to indicate a remote file, as in + "//servername/path/to/file.txt". Note that only certain platforms can + actually open this file using QFile::open(). + + \sa toLocalFile(), isLocalFile(), QDir::toNativeSeparators +*/ +QUrl QUrl::fromLocalFile(const QString &localFile) +{ + QUrl url; + url.setScheme(QLatin1String("file")); + QString deslashified = QDir::fromNativeSeparators(localFile); + + // magic for drives on windows + if (deslashified.length() > 1 && deslashified.at(1) == QLatin1Char(':') && deslashified.at(0) != QLatin1Char('/')) { + url.setPath(QLatin1Char('/') + deslashified); + // magic for shared drive on windows + } else if (deslashified.startsWith(QLatin1String("//"))) { + int indexOfPath = deslashified.indexOf(QLatin1Char('/'), 2); + url.setHost(deslashified.mid(2, indexOfPath - 2)); + if (indexOfPath > 2) + url.setPath(deslashified.right(deslashified.length() - indexOfPath)); + } else { + url.setPath(deslashified); + } + + return url; +} + +/*! + Returns the path of this URL formatted as a local file path. The path + returned will use forward slashes, even if it was originally created + from one with backslashes. + + If this URL contains a non-empty hostname, it will be encoded in the + returned value in the form found on SMB networks (for example, + "//servername/path/to/file.txt"). + + \sa fromLocalFile(), isLocalFile() +*/ +QString QUrl::toLocalFile() const +{ + // the call to isLocalFile() also ensures that we're parsed + if (!isLocalFile()) + return QString(); + + QString tmp; + QString ourPath = path(); + + // magic for shared drive on windows + if (!d->host.isEmpty()) { + tmp = QLatin1String("//") + d->host + (ourPath.length() > 0 && ourPath.at(0) != QLatin1Char('/') + ? QLatin1Char('/') + ourPath : ourPath); + } else { + tmp = ourPath; + // magic for drives on windows + if (ourPath.length() > 2 && ourPath.at(0) == QLatin1Char('/') && ourPath.at(2) == QLatin1Char(':')) + tmp.remove(0, 1); + } + + return tmp; +} + +/*! + \since 4.7 + Returns true if this URL is pointing to a local file path. A URL is a + local file path if the scheme is "file". + + Note that this function considers URLs with hostnames to be local file + paths, even if the eventual file path cannot be opened with + QFile::open(). + + \sa fromLocalFile(), toLocalFile() +*/ +bool QUrl::isLocalFile() const +{ + if (!d) return false; + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + if (d->scheme.compare(QLatin1String("file"), Qt::CaseInsensitive) != 0) + return false; // not file + return true; +} + +/*! + Returns true if this URL is a parent of \a childUrl. \a childUrl is a child + of this URL if the two URLs share the same scheme and authority, + and this URL's path is a parent of the path of \a childUrl. +*/ +bool QUrl::isParentOf(const QUrl &childUrl) const +{ + QString childPath = childUrl.path(); + + if (!d) + return ((childUrl.scheme().isEmpty()) + && (childUrl.authority().isEmpty()) + && childPath.length() > 0 && childPath.at(0) == QLatin1Char('/')); + + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + QString ourPath = path(); + + return ((childUrl.scheme().isEmpty() || d->scheme == childUrl.scheme()) + && (childUrl.authority().isEmpty() || d->authority() == childUrl.authority()) + && childPath.startsWith(ourPath) + && ((ourPath.endsWith(QLatin1Char('/')) && childPath.length() > ourPath.length()) + || (!ourPath.endsWith(QLatin1Char('/')) + && childPath.length() > ourPath.length() && childPath.at(ourPath.length()) == QLatin1Char('/')))); +} + +/*! + \fn void QUrl::setProtocol(const QString &s) + + Use setScheme() instead. +*/ + +/*! + \fn void QUrl::setUser(const QString &s) + + Use setUserName() instead. +*/ + +/*! + \fn bool QUrl::hasUser() const + + Use !userName().isEmpty() instead. +*/ + +/*! + \fn bool QUrl::hasPassword() const + + Use !password().isEmpty() instead. +*/ + +/*! + \fn bool QUrl::hasHost() const + + Use !host().isEmpty() instead. +*/ + +/*! + \fn bool QUrl::hasPort() const + + Use port() != -1 instead. +*/ + +/*! + \fn bool QUrl::hasPath() const + + Use !path().isEmpty() instead. +*/ + +/*! + \fn void QUrl::setQuery(const QString &txt) + + Use setEncodedQuery() instead. +*/ + +/*! + \fn void QUrl::setRef(const QString &txt) + + Use setFragment() instead. +*/ + +/*! + \fn bool QUrl::hasRef() const + + Use !fragment().isEmpty() instead. +*/ + +/*! + \fn void QUrl::addPath(const QString &p) + + Use setPath() instead. +*/ + +/*! + \fn void QUrl::setFileName(const QString &txt) + + Use setPath() instead. +*/ + +/*! + \fn void QUrl::decode(QString &url) + + Use fromPercentEncoding() instead. +*/ + +/*! + \fn void QUrl::encode(QString &url) + + Use toPercentEncoding() instead. +*/ + +/*! + \fn bool QUrl::cdUp() + + Use resolved("..") instead. + + \oldcode + QUrl url("http://example.com/Developer/"); + url.cdUp(); + \newcode + QUrl url("http://example.com/Developer/"); + url = url.resolved(".."); + \endcode +*/ + +/*! + \fn bool QUrl::isRelativeUrl(const QString &url) + + Use isRelative() instead. +*/ + +/*! + \fn void QUrl::reset() + + Use clear() instead. +*/ + +/*! + \fn QUrl::operator QString() const + + Use toString() instead. +*/ + +/*! + \fn QString QUrl::protocol() const + + Use scheme() instead. +*/ + +/*! + \fn QString QUrl::user() const + + Use userName() instead. +*/ + +/*! + \fn QString QUrl::query() const + + Use encodedQuery() instead. +*/ + +/*! + \fn QString QUrl::ref() const + + Use fragment() instead. +*/ + +/*! + \fn QString QUrl::fileName() const + + Use QFileInfo(path()).fileName() instead. +*/ + +/*! + \fn QString QUrl::dirPath() const + + Use QFileInfo(path()).absolutePath() or QFileInfo(path()) instead. +*/ + +#ifdef QT3_SUPPORT +void QUrl::setFileName(const QString &txt) +{ + QFileInfo fileInfo(path()); + fileInfo.setFile(txt); + setPath(fileInfo.filePath()); +} + +QString QUrl::fileName() const +{ + QFileInfo fileInfo(path()); + return fileInfo.fileName(); +} + +QString QUrl::dirPath() const +{ + QFileInfo fileInfo(path()); + if (fileInfo.isAbsolute()) { + QString absPath = fileInfo.absolutePath(); +#ifdef Q_OS_WIN + if (absPath.size() > 1 && absPath.at(1) == QLatin1Char(':')) + absPath = absPath.mid(2); +#endif + return absPath; + } + return fileInfo.path(); +} +#endif + + +#ifndef QT_NO_DATASTREAM +/*! \relates QUrl + + Writes url \a url to the stream \a out and returns a reference + to the stream. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ +QDataStream &operator<<(QDataStream &out, const QUrl &url) +{ + QByteArray u = url.toEncoded(); + out << u; + return out; +} + +/*! \relates QUrl + + Reads a url into \a url from the stream \a in and returns a + reference to the stream. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ +QDataStream &operator>>(QDataStream &in, QUrl &url) +{ + QByteArray u; + in >> u; + url = QUrl::fromEncoded(u); + return in; +} +#endif // QT_NO_DATASTREAM + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QUrl &url) +{ + d.maybeSpace() << "QUrl(" << url.toString() << ')'; + return d.space(); +} +#endif + +/*! + \since 4.2 + + Returns a text string that explains why an URL is invalid in the case being; + otherwise returns an empty string. +*/ +QString QUrl::errorString() const +{ + if (!d) + return QLatin1String(QT_TRANSLATE_NOOP(QUrl, "Invalid URL \"\": ")); // XXX not a good message, but the one an empty URL produces + return d->createErrorString(); +} + +/*! + \typedef QUrl::DataPtr + \internal +*/ + +/*! + \fn DataPtr &QUrl::data_ptr() + \internal +*/ + +// The following code has the following copyright: +/* + Copyright (C) Research In Motion Limited 2009. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Research In Motion Limited nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY Research In Motion Limited ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Research In Motion Limited BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + + +/*! + Returns a valid URL from a user supplied \a userInput string if one can be + deducted. In the case that is not possible, an invalid QUrl() is returned. + + \since 4.6 + + Most applications that can browse the web, allow the user to input a URL + in the form of a plain string. This string can be manually typed into + a location bar, obtained from the clipboard, or passed in via command + line arguments. + + When the string is not already a valid URL, a best guess is performed, + making various web related assumptions. + + In the case the string corresponds to a valid file path on the system, + a file:// URL is constructed, using QUrl::fromLocalFile(). + + If that is not the case, an attempt is made to turn the string into a + http:// or ftp:// URL. The latter in the case the string starts with + 'ftp'. The result is then passed through QUrl's tolerant parser, and + in the case or success, a valid QUrl is returned, or else a QUrl(). + + \section1 Examples: + + \list + \o qt.nokia.com becomes http://qt.nokia.com + \o ftp.qt.nokia.com becomes ftp://ftp.qt.nokia.com + \o hostname becomes http://hostname + \o /home/user/test.html becomes file:///home/user/test.html + \endlist + + \section2 Tips to avoid erroneous character conversion when dealing with + URLs and strings: + + \list + \o When creating an URL QString from a QByteArray or a char*, always use + QString::fromUtf8(). + \o Favor the use of QUrl::fromEncoded() and QUrl::toEncoded() instead of + QUrl(string) and QUrl::toString() when converting QUrl to/from string. + \endlist +*/ +QUrl QUrl::fromUserInput(const QString &userInput) +{ + QString trimmedString = userInput.trimmed(); + + // Check first for files, since on Windows drive letters can be interpretted as schemes + if (QDir::isAbsolutePath(trimmedString)) + return QUrl::fromLocalFile(trimmedString); + + QUrl url = QUrl::fromEncoded(trimmedString.toUtf8(), QUrl::TolerantMode); + QUrl urlPrepended = QUrl::fromEncoded("http://" + trimmedString.toUtf8(), QUrl::TolerantMode); + + // Check the most common case of a valid url with scheme and host + // We check if the port would be valid by adding the scheme to handle the case host:port + // where the host would be interpretted as the scheme + if (url.isValid() + && !url.scheme().isEmpty() + && (!url.host().isEmpty() || !url.path().isEmpty()) + && urlPrepended.port() == -1) + return url; + + // Else, try the prepended one and adjust the scheme from the host name + if (urlPrepended.isValid() && (!urlPrepended.host().isEmpty() || !urlPrepended.path().isEmpty())) + { + int dotIndex = trimmedString.indexOf(QLatin1Char('.')); + const QString hostscheme = trimmedString.left(dotIndex).toLower(); + if (hostscheme == QLatin1String("ftp")) + urlPrepended.setScheme(QLatin1String("ftp")); + return urlPrepended; + } + + return QUrl(); +} +// end of BSD code + +QT_END_NAMESPACE diff --git a/src/corelib/io/qurl.h b/src/corelib/io/qurl.h new file mode 100644 index 0000000000..7534b8d474 --- /dev/null +++ b/src/corelib/io/qurl.h @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QURL_H +#define QURL_H + +#include <QtCore/qbytearray.h> +#include <QtCore/qobjectdefs.h> +#include <QtCore/qpair.h> +#include <QtCore/qstring.h> +#include <QtCore/qhash.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QUrlPrivate; +class QDataStream; + +class Q_CORE_EXPORT QUrl +{ +public: + enum ParsingMode { + TolerantMode, + StrictMode + }; + + // encoding / toString values + enum FormattingOption { + None = 0x0, + RemoveScheme = 0x1, + RemovePassword = 0x2, + RemoveUserInfo = RemovePassword | 0x4, + RemovePort = 0x8, + RemoveAuthority = RemoveUserInfo | RemovePort | 0x10, + RemovePath = 0x20, + RemoveQuery = 0x40, + RemoveFragment = 0x80, + // 0x100: private: normalized + + StripTrailingSlash = 0x10000 + }; + Q_DECLARE_FLAGS(FormattingOptions, FormattingOption) + + QUrl(); +#ifdef QT_NO_URL_CAST_FROM_STRING + explicit +#endif + QUrl(const QString &url); + QUrl(const QString &url, ParsingMode mode); + // ### Qt 5: merge the two constructors, with mode = TolerantMode + QUrl(const QUrl ©); + QUrl &operator =(const QUrl ©); +#ifndef QT_NO_URL_CAST_FROM_STRING + QUrl &operator =(const QString &url); +#endif +#ifdef Q_COMPILER_RVALUE_REFS + inline QUrl &operator=(QUrl &&other) + { qSwap(d, other.d); return *this; } +#endif + ~QUrl(); + + inline void swap(QUrl &other) { qSwap(d, other.d); } + + void setUrl(const QString &url); + void setUrl(const QString &url, ParsingMode mode); + // ### Qt 5: merge the two setUrl() functions, with mode = TolerantMode + void setEncodedUrl(const QByteArray &url); + void setEncodedUrl(const QByteArray &url, ParsingMode mode); + // ### Qt 5: merge the two setEncodedUrl() functions, with mode = TolerantMode + + bool isValid() const; + + bool isEmpty() const; + + void clear(); + + void setScheme(const QString &scheme); + QString scheme() const; + + void setAuthority(const QString &authority); + QString authority() const; + + void setUserInfo(const QString &userInfo); + QString userInfo() const; + + void setUserName(const QString &userName); + QString userName() const; + void setEncodedUserName(const QByteArray &userName); + QByteArray encodedUserName() const; + + void setPassword(const QString &password); + QString password() const; + void setEncodedPassword(const QByteArray &password); + QByteArray encodedPassword() const; + + void setHost(const QString &host); + QString host() const; + void setEncodedHost(const QByteArray &host); + QByteArray encodedHost() const; + + void setPort(int port); + int port() const; + int port(int defaultPort) const; + // ### Qt 5: merge the two port() functions, with defaultPort = -1 + + void setPath(const QString &path); + QString path() const; + void setEncodedPath(const QByteArray &path); + QByteArray encodedPath() const; + + bool hasQuery() const; + + void setEncodedQuery(const QByteArray &query); + QByteArray encodedQuery() const; + + void setQueryDelimiters(char valueDelimiter, char pairDelimiter); + char queryValueDelimiter() const; + char queryPairDelimiter() const; + + void setQueryItems(const QList<QPair<QString, QString> > &query); + void addQueryItem(const QString &key, const QString &value); + QList<QPair<QString, QString> > queryItems() const; + bool hasQueryItem(const QString &key) const; + QString queryItemValue(const QString &key) const; + QStringList allQueryItemValues(const QString &key) const; + void removeQueryItem(const QString &key); + void removeAllQueryItems(const QString &key); + + void setEncodedQueryItems(const QList<QPair<QByteArray, QByteArray> > &query); + void addEncodedQueryItem(const QByteArray &key, const QByteArray &value); + QList<QPair<QByteArray, QByteArray> > encodedQueryItems() const; + bool hasEncodedQueryItem(const QByteArray &key) const; + QByteArray encodedQueryItemValue(const QByteArray &key) const; + QList<QByteArray> allEncodedQueryItemValues(const QByteArray &key) const; + void removeEncodedQueryItem(const QByteArray &key); + void removeAllEncodedQueryItems(const QByteArray &key); + + void setFragment(const QString &fragment); + QString fragment() const; + void setEncodedFragment(const QByteArray &fragment); + QByteArray encodedFragment() const; + bool hasFragment() const; + + QUrl resolved(const QUrl &relative) const; + + bool isRelative() const; + bool isParentOf(const QUrl &url) const; + + static QUrl fromLocalFile(const QString &localfile); + QString toLocalFile() const; + bool isLocalFile() const; + + QString toString(FormattingOptions options = None) const; + + QByteArray toEncoded(FormattingOptions options = None) const; + static QUrl fromEncoded(const QByteArray &url); + static QUrl fromEncoded(const QByteArray &url, ParsingMode mode); + // ### Qt 5: merge the two fromEncoded() functions, with mode = TolerantMode + + static QUrl fromUserInput(const QString &userInput); + + void detach(); + bool isDetached() const; + + bool operator <(const QUrl &url) const; + bool operator ==(const QUrl &url) const; + bool operator !=(const QUrl &url) const; + + static QString fromPercentEncoding(const QByteArray &); + static QByteArray toPercentEncoding(const QString &, + const QByteArray &exclude = QByteArray(), + const QByteArray &include = QByteArray()); + static QString fromPunycode(const QByteArray &); + static QByteArray toPunycode(const QString &); + static QString fromAce(const QByteArray &); + static QByteArray toAce(const QString &); + static QStringList idnWhitelist(); + static void setIdnWhitelist(const QStringList &); + +#if defined QT3_SUPPORT + inline QT3_SUPPORT QString protocol() const { return scheme(); } + inline QT3_SUPPORT void setProtocol(const QString &s) { setScheme(s); } + inline QT3_SUPPORT void setUser(const QString &s) { setUserName(s); } + inline QT3_SUPPORT QString user() const { return userName(); } + inline QT3_SUPPORT bool hasUser() const { return !userName().isEmpty(); } + inline QT3_SUPPORT bool hasPassword() const { return !password().isEmpty(); } + inline QT3_SUPPORT bool hasHost() const { return !host().isEmpty(); } + inline QT3_SUPPORT bool hasPort() const { return port() != -1; } + inline QT3_SUPPORT bool hasPath() const { return !path().isEmpty(); } + inline QT3_SUPPORT void setQuery(const QString &txt) + { + setEncodedQuery(txt.toLatin1()); + } + inline QT3_SUPPORT QString query() const + { + return QString::fromLatin1(encodedQuery().constData()); + } + inline QT3_SUPPORT QString ref() const { return fragment(); } + inline QT3_SUPPORT void setRef(const QString &txt) { setFragment(txt); } + inline QT3_SUPPORT bool hasRef() const { return !fragment().isEmpty(); } + inline QT3_SUPPORT void addPath(const QString &p) { setPath(path() + QLatin1Char('/') + p); } + QT3_SUPPORT void setFileName(const QString &txt); + QT3_SUPPORT QString fileName() const; + QT3_SUPPORT QString dirPath() const; + static inline QT3_SUPPORT void decode(QString &url) + { + url = QUrl::fromPercentEncoding(url.toLatin1()); + } + static inline QT3_SUPPORT void encode(QString &url) + { + url = QString::fromLatin1(QUrl::toPercentEncoding(url).constData()); + } + inline QT3_SUPPORT operator QString() const { return toString(); } + inline QT3_SUPPORT bool cdUp() + { + *this = resolved(QUrl(QLatin1String(".."))); + return true; + } + static inline QT3_SUPPORT bool isRelativeUrl(const QString &url) + { + return QUrl(url).isRelative(); + } +#endif + + QString errorString() const; + +protected: +#if defined (QT3_SUPPORT) + inline QT3_SUPPORT void reset() { clear(); } +#endif + +private: + QUrlPrivate *d; +public: + typedef QUrlPrivate * DataPtr; + inline DataPtr &data_ptr() { return d; } +}; + +inline uint qHash(const QUrl &url) +{ + return qHash(url.toEncoded(QUrl::FormattingOption(0x100))); +} + +Q_DECLARE_TYPEINFO(QUrl, Q_MOVABLE_TYPE); +Q_DECLARE_SHARED(QUrl) +Q_DECLARE_OPERATORS_FOR_FLAGS(QUrl::FormattingOptions) + +#ifndef QT_NO_DATASTREAM +Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QUrl &); +Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QUrl &); +#endif + +#ifndef QT_NO_DEBUG_STREAM +Q_CORE_EXPORT QDebug operator<<(QDebug, const QUrl &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QURL_H diff --git a/src/corelib/io/qwindowspipewriter.cpp b/src/corelib/io/qwindowspipewriter.cpp new file mode 100644 index 0000000000..5c6728aad5 --- /dev/null +++ b/src/corelib/io/qwindowspipewriter.cpp @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowspipewriter_p.h" +#include <string.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_THREAD + +QWindowsPipeWriter::QWindowsPipeWriter(HANDLE pipe, QObject * parent) + : QThread(parent), + writePipe(INVALID_HANDLE_VALUE), + quitNow(false), + hasWritten(false) +{ +#if !defined(Q_OS_WINCE) || (_WIN32_WCE >= 0x600) + DuplicateHandle(GetCurrentProcess(), pipe, GetCurrentProcess(), + &writePipe, 0, FALSE, DUPLICATE_SAME_ACCESS); +#else + Q_UNUSED(pipe); + writePipe = GetCurrentProcess(); +#endif +} + +QWindowsPipeWriter::~QWindowsPipeWriter() +{ + lock.lock(); + quitNow = true; + waitCondition.wakeOne(); + lock.unlock(); + if (!wait(100)) + terminate(); +#if !defined(Q_OS_WINCE) || (_WIN32_WCE >= 0x600) + CloseHandle(writePipe); +#endif +} + +bool QWindowsPipeWriter::waitForWrite(int msecs) +{ + QMutexLocker locker(&lock); + bool hadWritten = hasWritten; + hasWritten = false; + if (hadWritten) + return true; + if (!waitCondition.wait(&lock, msecs)) + return false; + hadWritten = hasWritten; + hasWritten = false; + return hadWritten; +} + +qint64 QWindowsPipeWriter::write(const char *ptr, qint64 maxlen) +{ + if (!isRunning()) + return -1; + + QMutexLocker locker(&lock); + data.append(QByteArray(ptr, maxlen)); + waitCondition.wakeOne(); + return maxlen; +} + +void QWindowsPipeWriter::run() +{ + OVERLAPPED overl; + memset(&overl, 0, sizeof overl); + overl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + forever { + lock.lock(); + while(data.isEmpty() && (!quitNow)) { + waitCondition.wakeOne(); + waitCondition.wait(&lock); + } + + if (quitNow) { + lock.unlock(); + quitNow = false; + break; + } + + QByteArray copy = data; + + lock.unlock(); + + const char *ptrData = copy.data(); + qint64 maxlen = copy.size(); + qint64 totalWritten = 0; + overl.Offset = 0; + overl.OffsetHigh = 0; + while ((!quitNow) && totalWritten < maxlen) { + DWORD written = 0; + if (!WriteFile(writePipe, ptrData + totalWritten, + maxlen - totalWritten, &written, &overl)) { + + if (GetLastError() == 0xE8/*NT_STATUS_INVALID_USER_BUFFER*/) { + // give the os a rest + msleep(100); + continue; + } +#ifndef Q_OS_WINCE + if (GetLastError() == ERROR_IO_PENDING) { + if (!GetOverlappedResult(writePipe, &overl, &written, TRUE)) { + CloseHandle(overl.hEvent); + return; + } + } else { + CloseHandle(overl.hEvent); + return; + } +#else + return; +#endif + } + totalWritten += written; +#if defined QPIPEWRITER_DEBUG + qDebug("QWindowsPipeWriter::run() wrote %d %d/%d bytes", + written, int(totalWritten), int(maxlen)); +#endif + lock.lock(); + data.remove(0, written); + hasWritten = true; + lock.unlock(); + } + emit bytesWritten(totalWritten); + emit canWrite(); + } + CloseHandle(overl.hEvent); +} + +#endif //QT_NO_THREAD + +QT_END_NAMESPACE diff --git a/src/corelib/io/qwindowspipewriter_p.h b/src/corelib/io/qwindowspipewriter_p.h new file mode 100644 index 0000000000..9ca0e82c35 --- /dev/null +++ b/src/corelib/io/qwindowspipewriter_p.h @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSPIPEWRITER_P_H +#define QWINDOWSPIPEWRITER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qdatetime.h> +#include <qthread.h> +#include <qmutex.h> +#include <qwaitcondition.h> +#include <qt_windows.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +#ifndef QT_NO_THREAD + +#define SLEEPMIN 10 +#define SLEEPMAX 500 + +class QIncrementalSleepTimer +{ + +public: + QIncrementalSleepTimer(int msecs) + : totalTimeOut(msecs) + , nextSleep(qMin(SLEEPMIN, totalTimeOut)) + { + if (totalTimeOut == -1) + nextSleep = SLEEPMIN; + timer.start(); + } + + int nextSleepTime() + { + int tmp = nextSleep; + nextSleep = qMin(nextSleep * 2, qMin(SLEEPMAX, timeLeft())); + return tmp; + } + + int timeLeft() const + { + if (totalTimeOut == -1) + return SLEEPMAX; + return qMax(totalTimeOut - timer.elapsed(), 0); + } + + bool hasTimedOut() const + { + if (totalTimeOut == -1) + return false; + return timer.elapsed() >= totalTimeOut; + } + + void resetIncrements() + { + nextSleep = qMin(SLEEPMIN, timeLeft()); + } + +private: + QTime timer; + int totalTimeOut; + int nextSleep; +}; + +class Q_CORE_EXPORT QWindowsPipeWriter : public QThread +{ + Q_OBJECT + +Q_SIGNALS: + void canWrite(); + void bytesWritten(qint64 bytes); + +public: + QWindowsPipeWriter(HANDLE writePipe, QObject * parent = 0); + ~QWindowsPipeWriter(); + + bool waitForWrite(int msecs); + qint64 write(const char *data, qint64 maxlen); + + qint64 bytesToWrite() const + { + QMutexLocker locker(&lock); + return data.size(); + } + + bool hadWritten() const + { + return hasWritten; + } + +protected: + void run(); + +private: + QByteArray data; + QWaitCondition waitCondition; + mutable QMutex lock; + HANDLE writePipe; + volatile bool quitNow; + bool hasWritten; +}; + +#endif //QT_NO_THREAD + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_PROCESS |