diff options
author | axis <qt-info@nokia.com> | 2009-04-24 11:34:15 (GMT) |
---|---|---|
committer | axis <qt-info@nokia.com> | 2009-04-24 11:34:15 (GMT) |
commit | 8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76 (patch) | |
tree | a17e1a767a89542ab59907462206d7dcf2e504b2 /src/corelib/io | |
download | Qt-8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76.zip Qt-8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76.tar.gz Qt-8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76.tar.bz2 |
Long live Qt for S60!
Diffstat (limited to 'src/corelib/io')
69 files changed, 46750 insertions, 0 deletions
diff --git a/src/corelib/io/io.pri b/src/corelib/io/io.pri new file mode 100644 index 0000000..845dc2e --- /dev/null +++ b/src/corelib/io/io.pri @@ -0,0 +1,88 @@ +# Qt core io module + +HEADERS += \ + io/qabstractfileengine.h \ + io/qabstractfileengine_p.h \ + io/qbuffer.h \ + io/qdatastream.h \ + io/qdebug.h \ + io/qdir.h \ + io/qdiriterator.h \ + io/qfile.h \ + io/qfileinfo.h \ + io/qfileinfo_p.h \ + io/qiodevice.h \ + io/qiodevice_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 + +SOURCES += \ + io/qabstractfileengine.cpp \ + io/qbuffer.cpp \ + io/qdatastream.cpp \ + io/qdebug.cpp \ + io/qdir.cpp \ + io/qdiriterator.cpp \ + io/qfile.cpp \ + io/qfileinfo.cpp \ + io/qiodevice.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 + +win32 { + SOURCES += io/qsettings_win.cpp + SOURCES += io/qprocess_win.cpp + SOURCES += io/qfsfileengine_win.cpp + + SOURCES += io/qfsfileengine_iterator_win.cpp + SOURCES += io/qfilesystemwatcher_win.cpp + HEADERS += io/qfilesystemwatcher_win_p.h + HEADERS += io/qwindowspipewriter_p.h + SOURCES += io/qwindowspipewriter.cpp +} else:unix { + SOURCES += io/qfsfileengine_unix.cpp + SOURCES += io/qfsfileengine_iterator_unix.cpp + symbian:SOURCES += io/qprocess_symbian.cpp + else:SOURCES += io/qprocess_unix.cpp + mac:SOURCES += io/qsettings_mac.cpp + + linux-*:{ + SOURCES += \ + io/qfilesystemwatcher_inotify.cpp \ + io/qfilesystemwatcher_dnotify.cpp + + HEADERS += \ + io/qfilesystemwatcher_inotify_p.h \ + io/qfilesystemwatcher_dnotify_p.h + } + + 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 + } +} diff --git a/src/corelib/io/qabstractfileengine.cpp b/src/corelib/io/qabstractfileengine.cpp new file mode 100644 index 0000000..bedc121 --- /dev/null +++ b/src/corelib/io/qabstractfileengine.cpp @@ -0,0 +1,1219 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractfileengine.h" +#include "private/qabstractfileengine_p.h" +#include "qdatetime.h" +#include "qmutex.h" +#include "qvariant.h" +// built-in handlers +#include "qfsfileengine.h" +#include "qdiriterator.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() +*/ + +/* + All application-wide handlers are stored in this list. The mutex must be + acquired to ensure thread safety. + */ +Q_GLOBAL_STATIC_WITH_ARGS(QMutex, fileEngineHandlerMutex, (QMutex::Recursive)) +static bool qt_abstractfileenginehandlerlist_shutDown = false; +class QAbstractFileEngineHandlerList : public QList<QAbstractFileEngineHandler *> +{ +public: + ~QAbstractFileEngineHandlerList() + { + QMutexLocker 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() +{ + QMutexLocker locker(fileEngineHandlerMutex()); + fileEngineHandlers()->prepend(this); +} + +/*! + Destroys the file handler. This will automatically unregister the handler + from Qt. + */ +QAbstractFileEngineHandler::~QAbstractFileEngineHandler() +{ + QMutexLocker locker(fileEngineHandlerMutex()); + // Remove this handler from the handler list only if the list is valid. + if (!qt_abstractfileenginehandlerlist_shutDown) + fileEngineHandlers()->removeAll(this); +} + +/*! + \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) +{ + QMutexLocker locker(fileEngineHandlerMutex()); + + // check for registered handlers that can load the file + for (int i = 0; i < fileEngineHandlers()->size(); i++) { + if (QAbstractFileEngine *ret = fileEngineHandlers()->at(i)->create(fileName)) + return ret; + } + +#ifdef QT_BUILD_CORE_LIB + if (!fileName.startsWith(QLatin1Char('/'))) { + int prefixSeparator = fileName.indexOf(QLatin1Char(':')); + if (prefixSeparator > 1) { + QString prefix = fileName.left(prefixSeparator); + QString fileNameWithoutPrefix = fileName.mid(prefixSeparator + 1).prepend(QLatin1Char('/')); + const QStringList &paths = QDir::searchPaths(prefix); + for (int i = 0; i < paths.count(); i++) { + QString path = paths.at(i); + path.append(fileNameWithoutPrefix); + QAbstractFileEngine *engine = create(path); + if (engine && (engine->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::ExistsFlag)) { + return engine; + } + delete engine; + } + } + } +#endif + +#ifdef QT_NO_FSFILEENGINE + return 0; +#else + // fall back to regular file engine + return new QFSFileEngine(fileName); +#endif +} + +/*! + \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. + + \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() +{ + delete d_ptr; + d_ptr = 0; +} + +/*! + \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 virual 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 interator 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() +{ + delete d; +} + +/*! + 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 that 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 0000000..d742467 --- /dev/null +++ b/src/corelib/io/qabstractfileengine.h @@ -0,0 +1,247 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 + }; + 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 &); + + 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); + 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 0000000..34a5a02 --- /dev/null +++ b/src/corelib/io/qabstractfileengine_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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) +}; + +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 0000000..a34f7ba --- /dev/null +++ b/src/corelib/io/qbuffer.cpp @@ -0,0 +1,475 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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; + +#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 + +/*! + \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)")) + 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 0000000..37aefb2 --- /dev/null +++ b/src/corelib/io/qbuffer.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 0000000..9990696 --- /dev/null +++ b/src/corelib/io/qdatastream.cpp @@ -0,0 +1,1260 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdatastream.h" + +#ifndef QT_NO_DATASTREAM +#include "qbuffer.h" +#include "qstring.h" +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QDataStream + \reentrant + \brief The QDataStream class provides serialization of binary data + to a QIODevice. + + \ingroup io + \mainclass + + 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 the \l{Format of the QDataStream + operators}. + + 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. + + \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::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. +*/ + +/***************************************************************************** + 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 + +enum { + DefaultStreamVersion = QDataStream::Qt_4_5 +}; + +// ### 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. + + \sa setDevice(), unsetDevice() +*/ + +/*! + void QDataStream::setDevice(QIODevice *d) + + Sets the I/O device to \a d. + + \sa device(), unsetDevice() +*/ + +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 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. + + \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 10 (Qt 4.5) + \omitvalue 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. + + In order 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.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 (noswap) { + if (dev->read((char *)&i, 2) != 2) { + i = 0; + setStatus(ReadPastEnd); + } + } else { + register uchar *p = (uchar *)(&i); + char b[2]; + if (dev->read(b, 2) == 2) { + *p++ = b[1]; + *p = b[0]; + } else { + setStatus(ReadPastEnd); + } + } + 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 (noswap) { + if (dev->read((char *)&i, 4) != 4) { + i = 0; + setStatus(ReadPastEnd); + } + } else { // swap bytes + uchar *p = (uchar *)(&i); + char b[4]; + if (dev->read(b, 4) == 4) { + *p++ = b[3]; + *p++ = b[2]; + *p++ = b[1]; + *p = b[0]; + } else { + setStatus(ReadPastEnd); + } + } + 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 (noswap) { // no conversion needed + if (dev->read((char *)&i, 8) != 8) { + i = qint64(0); + setStatus(ReadPastEnd); + } + } else { // swap bytes + uchar *p = (uchar *)(&i); + char b[8]; + if (dev->read(b, 8) == 8) { + *p++ = b[7]; + *p++ = b[6]; + *p++ = b[5]; + *p++ = b[4]; + *p++ = b[3]; + *p++ = b[2]; + *p++ = b[1]; + *p = b[0]; + } else { + setStatus(ReadPastEnd); + } + } + 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 32-bit floating point number from the stream into \a f, + using the standard IEEE 754 format. Returns a reference to the + stream. +*/ + +QDataStream &QDataStream::operator>>(float &f) +{ + f = 0.0f; + CHECK_STREAM_PRECOND(*this) + if (noswap) { + if (dev->read((char *)&f, 4) != 4) { + f = 0.0f; + setStatus(ReadPastEnd); + } + } else { // swap bytes + uchar *p = (uchar *)(&f); + char b[4]; + if (dev->read(b, 4) == 4) { + *p++ = b[3]; + *p++ = b[2]; + *p++ = b[1]; + *p = b[0]; + } else { + setStatus(ReadPastEnd); + } + } + return *this; +} + +#if defined(Q_DOUBLE_FORMAT) +#define Q_DF(x) Q_DOUBLE_FORMAT[(x)] - '0' +#endif + +/*! + \overload + + Reads a 64-bit floating point number from the stream into \a f, + using the standard IEEE 754 format. Returns a reference to the + stream. +*/ + +QDataStream &QDataStream::operator>>(double &f) +{ + f = 0.0; + CHECK_STREAM_PRECOND(*this) +#ifndef Q_DOUBLE_FORMAT + if (noswap) { + if (dev->read((char *)&f, 8) != 8) { + f = 0.0; + setStatus(ReadPastEnd); + } + } else { // swap bytes + register uchar *p = (uchar *)(&f); + char b[8]; + if (dev->read(b, 8) == 8) { + *p++ = b[7]; + *p++ = b[6]; + *p++ = b[5]; + *p++ = b[4]; + *p++ = b[3]; + *p++ = b[2]; + *p++ = b[1]; + *p = b[0]; + } else { + setStatus(ReadPastEnd); + } + } +#else + //non-standard floating point format + register uchar *p = (uchar *)(&f); + 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)]; + } + } 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_PRECOND(*this) + dev->putChar(i); + 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_PRECOND(*this) + if (noswap) { + dev->write((char *)&i, sizeof(qint16)); + } else { // swap bytes + register uchar *p = (uchar *)(&i); + char b[2]; + b[1] = *p++; + b[0] = *p; + dev->write(b, 2); + } + 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_PRECOND(*this) + if (noswap) { + dev->write((char *)&i, sizeof(qint32)); + } else { // swap bytes + register uchar *p = (uchar *)(&i); + char b[4]; + b[3] = *p++; + b[2] = *p++; + b[1] = *p++; + b[0] = *p; + dev->write(b, 4); + } + 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_PRECOND(*this) + if (version() < 6) { + quint32 i1 = i & 0xffffffff; + quint32 i2 = i >> 32; + *this << i2 << i1; + } else if (noswap) { // no conversion needed + dev->write((char *)&i, sizeof(qint64)); + } else { // swap bytes + register uchar *p = (uchar *)(&i); + char b[8]; + b[7] = *p++; + b[6] = *p++; + b[5] = *p++; + b[4] = *p++; + b[3] = *p++; + b[2] = *p++; + b[1] = *p++; + b[0] = *p; + dev->write(b, 8); + } + 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_PRECOND(*this) + dev->putChar(qint8(i)); + return *this; +} + +/*! + \overload + + Writes a 32-bit floating point number, \a f, to the stream using + the standard IEEE 754 format. Returns a reference to the stream. +*/ + +QDataStream &QDataStream::operator<<(float f) +{ + CHECK_STREAM_PRECOND(*this) + float g = f; // fixes float-on-stack problem + if (noswap) { // no conversion needed + dev->write((char *)&g, sizeof(float)); + } else { // swap bytes + register uchar *p = (uchar *)(&g); + char b[4]; + b[3] = *p++; + b[2] = *p++; + b[1] = *p++; + b[0] = *p; + dev->write(b, 4); + } + return *this; +} + + +/*! + \overload + + Writes a 64-bit floating point number, \a f, to the stream using + the standard IEEE 754 format. Returns a reference to the stream. +*/ + +QDataStream &QDataStream::operator<<(double f) +{ + CHECK_STREAM_PRECOND(*this) +#ifndef Q_DOUBLE_FORMAT + if (noswap) { + dev->write((char *)&f, sizeof(double)); + } else { + register uchar *p = (uchar *)(&f); + char b[8]; + b[7] = *p++; + b[6] = *p++; + b[5] = *p++; + b[4] = *p++; + b[3] = *p++; + b[2] = *p++; + b[1] = *p++; + b[0] = *p; + dev->write(b, 8); + } +#else + register uchar *p = (uchar *)(&f); + 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; + } + dev->write(b, 8); +#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_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_PRECOND(-1) + return dev->write(s, len); +} + +/*! + \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 0000000..4c8295e --- /dev/null +++ b/src/corelib/io/qdatastream.h @@ -0,0 +1,427 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATASTREAM_H +#define QDATASTREAM_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; + +class QDataStreamPrivate; + +#ifndef QT_NO_DATASTREAM +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 +#if QT_VERSION >= 0x040600 +#error Add the datastream version for this Qt version + , Qt_4_6 = Qt_4_5 +#endif + }; + + enum ByteOrder { + BigEndian = QSysInfo::BigEndian, + LittleEndian = QSysInfo::LittleEndian + }; + + enum Status { + Ok, + ReadPastEnd, + ReadCorruptData + }; + + 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(); + + 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) + + 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; + 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/qdebug.cpp b/src/corelib/io/qdebug.cpp new file mode 100644 index 0000000..611b19a --- /dev/null +++ b/src/corelib/io/qdebug.cpp @@ -0,0 +1,308 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 + \ingroup io + \mainclass + \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 0000000..8334146 --- /dev/null +++ b/src/corelib/io/qdebug.h @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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> + +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_message_output(stream->type, stream->buffer.toLocal8Bit().data()); + 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) ? "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; } + +#ifndef QT_NO_MEMBER_TEMPLATES + template<typename T> + inline QNoDebug &operator<<(const T &) { return *this; } +#endif +}; + +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(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 + +#ifdef QT_NO_MEMBER_TEMPLATES +template<typename T> +inline QNoDebug operator<<(QNoDebug debug, const T &) { return debug; } +#endif + +#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 0000000..0d82c2a --- /dev/null +++ b/src/corelib/io/qdir.cpp @@ -0,0 +1,2475 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qdir.h" +#include "qabstractfileengine.h" +#ifndef QT_NO_DEBUG_STREAM +#include "qdebug.h" +#endif +#include "qfsfileengine.h" +#include "qdatetime.h" +#include "qstring.h" +#include "qregexp.h" +#include "qvector.h" +#ifdef QT_BUILD_CORE_LIB +# include "qresource.h" +#endif + +#include "../kernel/qcoreglobaldata_p.h" +#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 +class QDirPrivate +{ + QDir *q_ptr; + Q_DECLARE_PUBLIC(QDir) + +protected: + QDirPrivate(QDir*, const QDir *copy=0); + ~QDirPrivate(); + + QString initFileEngine(const QString &file); + + void updateFileLists() const; + void sortFileList(QDir::SortFlags, QStringList &, QStringList *, QFileInfoList *) const; + +private: +#ifdef QT3_SUPPORT + QChar filterSepChar; + bool matchAllDirs; +#endif + static inline QChar 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 splitFilters(const QString &nameFilter, QChar sep=0) { + 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; + } + + struct Data { + inline Data() + : ref(1), fileEngine(0) + { clear(); } + inline Data(const Data ©) + : ref(1), path(copy.path), nameFilters(copy.nameFilters), sort(copy.sort), + filters(copy.filters), fileEngine(0) + { clear(); } + inline ~Data() + { delete fileEngine; } + + inline void clear() { + listsDirty = 1; + } + mutable QAtomicInt ref; + + QString path; + QStringList nameFilters; + QDir::SortFlags sort; + QDir::Filters filters; + + mutable QAbstractFileEngine *fileEngine; + + mutable uint listsDirty : 1; + mutable QStringList files; + mutable QFileInfoList fileInfos; + } *data; + inline void setPath(const QString &p) + { + detach(false); + QString path = p; + if ((path.endsWith(QLatin1Char('/')) || path.endsWith(QLatin1Char('\\'))) + && path.length() > 1) { +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (!(path.length() == 3 && path.at(1) == QLatin1Char(':'))) +#endif + path.truncate(path.length() - 1); + } + if(!data->fileEngine || !QDir::isRelativePath(path)) + path = initFileEngine(path); + data->fileEngine->setFileName(path); + // set the path to be the qt friendly version so then we can operate on it using just / + data->path = data->fileEngine->fileName(QAbstractFileEngine::DefaultName); + data->clear(); + } + inline void reset() { + detach(); + data->clear(); + } + void detach(bool createFileEngine = true); +}; + +QDirPrivate::QDirPrivate(QDir *qq, const QDir *copy) : q_ptr(qq) +#ifdef QT3_SUPPORT + , filterSepChar(0) + , matchAllDirs(false) +#endif +{ + if(copy) { + copy->d_func()->data->ref.ref(); + data = copy->d_func()->data; + } else { + data = new QDirPrivate::Data; + data->clear(); + } +} + +QDirPrivate::~QDirPrivate() +{ + if (!data->ref.deref()) + delete data; + data = 0; + q_ptr = 0; +} + +/* For sorting */ +struct QDirSortItem { + QString filename_cache; + QString suffix_cache; + QFileInfo item; +}; +static int qt_cmp_si_sort_flags; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +#ifdef Q_OS_WINCE +static int __cdecl qt_cmp_si(const void *n1, const void *n2) +#else +static int qt_cmp_si(const void *n1, const void *n2) +#endif +{ + if (!n1 || !n2) + return 0; + + QDirSortItem* f1 = (QDirSortItem*)n1; + QDirSortItem* f2 = (QDirSortItem*)n2; + + if ((qt_cmp_si_sort_flags & QDir::DirsFirst) && (f1->item.isDir() != f2->item.isDir())) + return f1->item.isDir() ? -1 : 1; + if ((qt_cmp_si_sort_flags & QDir::DirsLast) && (f1->item.isDir() != f2->item.isDir())) + return f1->item.isDir() ? 1 : -1; + + 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 = (char*)n1 - (char*)n2; + + if (qt_cmp_si_sort_flags & QDir::Reversed) + return -r; + return r; +} + +#if defined(Q_C_CALLBACKS) +} +#endif + +inline void QDirPrivate::sortFileList(QDir::SortFlags sort, QStringList &l, + QStringList *names, QFileInfoList *infos) const +{ + if(names) + names->clear(); + if(infos) + infos->clear(); + if(!l.isEmpty()) { + QDirSortItem *si= new QDirSortItem[l.count()]; + int i; + for (i = 0; i < l.size(); ++i) { + QString path = data->path; + if (!path.isEmpty() && !path.endsWith(QLatin1Char('/'))) + path += QLatin1Char('/'); + si[i].item = QFileInfo(path + l.at(i)); + } + qt_cmp_si_sort_flags = sort; + if ((sort & QDir::SortByMask) != QDir::Unsorted) + qsort(si, i, sizeof(si[0]), qt_cmp_si); + // put them back in the list(s) + for (int j = 0; j<i; j++) { + if(infos) + infos->append(si[j].item); + if(names) + names->append(si[j].item.fileName()); + } + delete [] si; + } +} + +inline void QDirPrivate::updateFileLists() const +{ + if(data->listsDirty) { + QStringList l = data->fileEngine->entryList(data->filters, data->nameFilters); + sortFileList(data->sort, l, &data->files, &data->fileInfos); + data->listsDirty = 0; + } +} + +QString QDirPrivate::initFileEngine(const QString &path) +{ + detach(false); + delete data->fileEngine; + data->fileEngine = 0; + data->clear(); + data->fileEngine = QAbstractFileEngine::create(path); + return data->fileEngine->fileName(QAbstractFileEngine::DefaultName); +} + +void QDirPrivate::detach(bool createFileEngine) +{ + qAtomicDetach(data); + if (createFileEngine) { + delete data->fileEngine; + data->fileEngine = QAbstractFileEngine::create(data->path); + } +} + +/*! + \class QDir + \brief The QDir class provides access to directory structures and their contents. + + \ingroup io + \ingroup shared + \reentrant + \mainclass + + 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(this)) +{ + Q_D(QDir); + d->setPath(path.isEmpty() ? QString::fromLatin1(".") : path); + d->data->nameFilters = QStringList(QString::fromLatin1("*")); + d->data->filters = AllEntries; + d->data->sort = SortFlags(Name | IgnoreCase); +} + +/*! + 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(this)) +{ + Q_D(QDir); + d->setPath(path.isEmpty() ? QString::fromLatin1(".") : path); + d->data->nameFilters = QDir::nameFiltersFromString(nameFilter); + bool empty = d->data->nameFilters.isEmpty(); + if(!empty) { + empty = true; + for(int i = 0; i < d->data->nameFilters.size(); ++i) { + if(!d->data->nameFilters.at(i).isEmpty()) { + empty = false; + break; + } + } + } + if (empty) + d->data->nameFilters = QStringList(QString::fromLatin1("*")); + d->data->sort = sort; + d->data->filters = 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(new QDirPrivate(this, &dir)) +{ +} + +/*! + Destroys the QDir object frees up its resources. This has no + effect on the underlying directory in the file system. +*/ + +QDir::~QDir() +{ + delete d_ptr; + d_ptr = 0; +} + +/*! + 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) +{ + Q_D(QDir); + d->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 +{ + Q_D(const QDir); + return d->data->path; +} + +/*! + 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 +{ + Q_D(const QDir); + QString ret = d->data->path; + if (QDir::isRelativePath(ret)) + ret = absoluteFilePath(QString::fromLatin1("")); + return cleanPath(ret); +} + + +/*! + 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 +{ + Q_D(const QDir); + + if(!d->data->fileEngine) + return QLatin1String(""); + return cleanPath(d->data->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 +{ + Q_D(const QDir); + int pos = d->data->path.lastIndexOf(QLatin1Char('/')); + if (pos == -1) + return d->data->path; + return d->data->path.mid(pos + 1); +} + +/*! + 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 +{ + Q_D(const QDir); + if (isAbsolutePath(fileName)) + return QString(fileName); + + QString ret = d->data->path; + 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 +{ + Q_D(const QDir); + if (isAbsolutePath(fileName)) + return fileName; + if(!d->data->fileEngine) + return fileName; + + QString ret; +#ifndef QT_NO_FSFILEENGINE + if (isRelativePath(d->data->path)) //get pwd + ret = QFSFileEngine::currentPath(fileName); +#endif + if(!d->data->path.isEmpty() && d->data->path != QLatin1String(".")) { + if (!ret.isEmpty() && !ret.endsWith(QLatin1Char('/'))) + ret += QLatin1Char('/'); + ret += d->data->path; + } + if (!fileName.isEmpty()) { + if (!ret.isEmpty() && !ret.endsWith(QLatin1Char('/'))) + ret += QLatin1Char('/'); + ret += fileName; + } + return ret; +} + +/*! + 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 = 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; +} + +/*! + \obsolete + + Use QDir::toNativeSeparators() instead. +*/ +QString QDir::convertSeparators(const QString &pathName) +{ + return toNativeSeparators(pathName); +} + +/*! + \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) +{ + Q_D(QDir); + + if (dirName.isEmpty() || dirName == QLatin1String(".")) + return true; + QString newPath = d->data->path; + if (isAbsolutePath(dirName)) { + newPath = cleanPath(dirName); + } else { + if (isRoot()) { + if (dirName == QLatin1String("..")) + return false; + } else { + newPath += QLatin1Char('/'); + } + + newPath += dirName; + if (dirName.indexOf(QLatin1Char('/')) >= 0 + || d->data->path == QLatin1String(".") + || dirName == 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(); + } + } + } + { + QFileInfo fi(newPath); + if (!(fi.exists() && fi.isDir())) + return false; + } + + d->setPath(newPath); + refresh(); + 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 +{ + Q_D(const QDir); + + return d->data->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) +{ + Q_D(QDir); + d->detach(); + d->data->nameFilters = nameFilters; +} + +/*! + \obsolete + 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{:/}). + + Use QDir::addSearchPath() with a prefix instead. + + \sa {The Qt Resource System}, QResource::addSearchPath() +*/ + +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 +{ + Q_D(const QDir); + + return d->data->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 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 + under Unix). + \value Hidden List hidden files (on Unix, files starting with a .). + \value System List system files (on Unix, FIFOs, sockets and + device files) + \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) +{ + Q_D(QDir); + + d->detach(); + d->data->filters = filters; +} + +/*! + Returns the value set by setSorting() + + \sa setSorting() SortFlag +*/ + +QDir::SortFlags QDir::sorting() const +{ + Q_D(const QDir); + + return d->data->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) +{ + Q_D(QDir); + + d->detach(); + d->data->sort = sort; +} + + +/*! + Returns the total number of directories and files in the directory. + + Equivalent to entryList().count(). + + \sa operator[](), entryList() +*/ + +uint QDir::count() const +{ + Q_D(const QDir); + + d->updateFileLists(); + return d->data->files.count(); +} + +/*! + Returns the file name at position \a pos in the list of file + names. Equivalent to entryList().at(index). + + Returns an empty string if \a pos is out of range or if the + entryList() function failed. + + \sa count(), entryList() +*/ + +QString QDir::operator[](int pos) const +{ + Q_D(const QDir); + + d->updateFileLists(); + return d->data->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 +{ + Q_D(const QDir); + + return entryList(d->data->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 +{ + Q_D(const QDir); + + return entryInfoList(d->data->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 +{ + Q_D(const QDir); + + if (filters == NoFilter) + filters = d->data->filters; +#ifdef QT3_SUPPORT + if (d->matchAllDirs) + filters |= AllDirs; +#endif + if (sort == NoSort) + sort = d->data->sort; + if (filters == NoFilter && sort == NoSort && nameFilters == d->data->nameFilters) { + d->updateFileLists(); + return d->data->files; + } + QStringList l = d->data->fileEngine->entryList(filters, nameFilters); + if ((sort & QDir::SortByMask) == QDir::Unsorted) + return l; + + 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 +{ + Q_D(const QDir); + + if (filters == NoFilter) + filters = d->data->filters; +#ifdef QT3_SUPPORT + if (d->matchAllDirs) + filters |= AllDirs; +#endif + if (sort == NoSort) + sort = d->data->sort; + if (filters == NoFilter && sort == NoSort && nameFilters == d->data->nameFilters) { + d->updateFileLists(); + return d->data->fileInfos; + } + QFileInfoList ret; + QStringList l = d->data->fileEngine->entryList(filters, nameFilters); + d->sortFileList(sort, l, 0, &ret); + return ret; +} + +/*! + Creates a sub-directory called \a dirName. + + Returns true on success; otherwise returns false. + + \sa rmdir() +*/ + +bool QDir::mkdir(const QString &dirName) const +{ + Q_D(const QDir); + + if (dirName.isEmpty()) { + qWarning("QDir::mkdir: Empty or null file name(s)"); + return false; + } + if(!d->data->fileEngine) + return false; + + QString fn = filePath(dirName); + return d->data->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 +{ + Q_D(const QDir); + + if (dirName.isEmpty()) { + qWarning("QDir::rmdir: Empty or null file name(s)"); + return false; + } + if(!d->data->fileEngine) + return false; + + QString fn = filePath(dirName); + return d->data->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. + + \sa rmpath() +*/ + +bool QDir::mkpath(const QString &dirPath) const +{ + Q_D(const QDir); + + if (dirPath.isEmpty()) { + qWarning("QDir::mkpath: Empty or null file name(s)"); + return false; + } + if(!d->data->fileEngine) + return false; + + QString fn = filePath(dirPath); + return d->data->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 +{ + Q_D(const QDir); + + if (dirPath.isEmpty()) { + qWarning("QDir::rmpath: Empty or null file name(s)"); + return false; + } + if(!d->data->fileEngine) + return false; + + QString fn = filePath(dirPath); + return d->data->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 +{ + Q_D(const QDir); + + if(!d->data->fileEngine) + return false; + const QAbstractFileEngine::FileFlags info = d->data->fileEngine->fileFlags(QAbstractFileEngine::DirectoryType + |QAbstractFileEngine::PermsMask); + if(!(info & QAbstractFileEngine::DirectoryType)) + return false; + return info & QAbstractFileEngine::ReadUserPerm; +} + +/*! + \overload + + Returns true if the \e directory exists; otherwise returns false. + (If a file with the same name is found this function will return + false). + + \sa QFileInfo::exists(), QFile::exists() +*/ + +bool QDir::exists() const +{ + Q_D(const QDir); + + if(!d->data->fileEngine) + return false; + const QAbstractFileEngine::FileFlags info = + d->data->fileEngine->fileFlags( + QAbstractFileEngine::DirectoryType + | QAbstractFileEngine::ExistsFlag + | QAbstractFileEngine::Refresh); + if(!(info & QAbstractFileEngine::DirectoryType)) + return false; + return info & QAbstractFileEngine::ExistsFlag; +} + +/*! + 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 +{ + Q_D(const QDir); + + if(!d->data->fileEngine) + return true; + return d->data->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 +{ + Q_D(const QDir); + + if(!d->data->fileEngine) + return false; + return d->data->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() // ### What do the return values signify? +{ + Q_D(QDir); + + if(!d->data->fileEngine) + return false; + QString absolutePath = d->data->fileEngine->fileName(QAbstractFileEngine::AbsoluteName); + if(QDir::isRelativePath(absolutePath)) + return false; + d->detach(); + d->data->path = absolutePath; + d->data->fileEngine->setFileName(absolutePath); + if(!(d->data->fileEngine->fileFlags(QAbstractFileEngine::TypesMask) & QAbstractFileEngine::DirectoryType)) + return false; + 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_func(); + const QDirPrivate *other = dir.d_func(); + + if(d->data == other->data) + return true; + Q_ASSERT(d->data->fileEngine && other->data->fileEngine); + if(d->data->fileEngine->caseSensitive() != other->data->fileEngine->caseSensitive()) + return false; + if(d->data->filters == other->data->filters + && d->data->sort == other->data->sort + && d->data->nameFilters == other->data->nameFilters) { + QString dir1 = absolutePath(), dir2 = dir.absolutePath(); + if(!other->data->fileEngine->caseSensitive()) + return (dir1.toLower() == dir2.toLower()); + + return (dir1 == dir2); + + } + return false; +} + +/*! + Makes a copy of the \a dir object and assigns it to this QDir + object. +*/ + +QDir &QDir::operator=(const QDir &dir) +{ + if (this == &dir) + return *this; + + Q_D(QDir); + qAtomicAssign(d->data, dir.d_func()->data); + return *this; +} + +/*! + \overload + \obsolete + + Sets the directory path to the given \a path. + + Use setPath() instead. +*/ + +QDir &QDir::operator=(const QString &path) +{ + Q_D(QDir); + + d->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; + } + QString p = filePath(fileName); + return QFile::remove(p); +} + +/*! + 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) +{ + Q_D(QDir); + + if (oldName.isEmpty() || newName.isEmpty()) { + qWarning("QDir::rename: Empty or null file name(s)"); + return false; + } + if(!d->data->fileEngine) + 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 current 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; + } + QString tmp = filePath(name); + return QFile::exists(tmp); +} + +/*! + 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) +{ +#ifdef QT_NO_FSFILEENGINE + Q_UNUSED(path); + return false; +#else + return QFSFileEngine::setCurrentPath(path); +#endif +} + +/*! + \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(), home(), root(), temp() +*/ + +/*! + Returns the absolute path of the application's current directory. + + \sa current(), homePath(), rootPath(), tempPath() +*/ +QString QDir::currentPath() +{ +#ifdef QT_NO_FSFILEENGINE + return QString(); +#else + return QFSFileEngine::currentPath(); +#endif +} + +/*! + \fn QString QDir::currentDirPath() + Returns the absolute path of the application's current directory. + + Use currentPath() instead. + + \sa currentPath() +*/ + +/*! + \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() function is used, except in Symbian, where c:\\data is + returned. + + \sa home(), currentPath(), rootPath(), tempPath() +*/ +QString QDir::homePath() +{ +#ifdef QT_NO_FSFILEENGINE + return QString(); +#else + return cleanPath(QFSFileEngine::homePath()); +#endif +} + +/*! + \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 usually \c{/tmp}; 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() +{ +#ifdef QT_NO_FSFILEENGINE + return QString(); +#else + return cleanPath(QFSFileEngine::tempPath()); +#endif +} + +/*! + \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:/". + + \sa root(), drives(), currentPath(), homePath(), tempPath() +*/ +QString QDir::rootPath() +{ +#ifdef QT_NO_FSFILEENGINE + return QString(); +#else + return QFSFileEngine::rootPath(); +#endif +} + +/*! + \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.begin(); sit != filters.end(); ++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 + +/*! + 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(); + QVector<QChar> out(len); + 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; + if(used == len) + ret = name; + else + ret = QString(out.data(), used); + + // Strip away last slash except for root directories + if (ret.endsWith(QLatin1Char('/')) + && !(ret.size() == 1 || (ret.size() == 3 && ret.at(1) == QLatin1Char(':')))) + ret = ret.left(ret.length() - 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 +{ + Q_D(const QDir); + + d->data->clear(); +} + +/*! + \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 +{ + Q_D(const QDir); + return d->matchAllDirs; +} + + +/*! + \fn void QDir::setMatchAllDirs(bool on) + + Use setFilter() instead. +*/ +void QDir::setMatchAllDirs(bool on) +{ + Q_D(QDir); + d->matchAllDirs = on; +} + +/*! + Use nameFilters() instead. +*/ +QString QDir::nameFilter() const +{ + Q_D(const QDir); + + 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) +{ + Q_D(QDir); + + d->filterSepChar = QDirPrivate::getFilterSepChar(nameFilter); + setNameFilters(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 +#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"); + 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; +} + +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_END_NAMESPACE diff --git a/src/corelib/io/qdir.h b/src/corelib/io/qdir.h new file mode 100644 index 0000000..c4f6b1a --- /dev/null +++ b/src/corelib/io/qdir.h @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIR_H +#define QDIR_H + +#include <QtCore/qstring.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QDirPrivate; + +class Q_CORE_EXPORT QDir +{ +protected: + QDirPrivate *d_ptr; +private: + Q_DECLARE_PRIVATE(QDir) +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, + + 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); + + 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 +}; + +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/qdiriterator.cpp b/src/corelib/io/qdiriterator.cpp new file mode 100644 index 0000000..46c7dd8 --- /dev/null +++ b/src/corelib/io/qdiriterator.cpp @@ -0,0 +1,559 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 "qabstractfileengine.h" + +#include <QtCore/qset.h> +#include <QtCore/qstack.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_NAMESPACE + +class QDirIteratorPrivate +{ +public: + QDirIteratorPrivate(const QString &path, const QStringList &nameFilters, + QDir::Filters filters, QDirIterator::IteratorFlags flags); + ~QDirIteratorPrivate(); + + void pushSubDirectory(const QString &path, const QStringList &nameFilters, + QDir::Filters filters); + void advance(); + bool shouldFollowDirectory(const QFileInfo &); + bool matchesFilters(const QAbstractFileEngineIterator *it) const; + + QSet<QString> visitedLinks; + QAbstractFileEngine *engine; + QStack<QAbstractFileEngineIterator *> fileEngineIterators; + QString path; + QFileInfo fileInfo; + QString currentFilePath; + QDirIterator::IteratorFlags iteratorFlags; + QDir::Filters filters; + QStringList nameFilters; + bool followNextDir; + bool first; + bool done; + + QDirIterator *q; +}; + +/*! + \internal +*/ +QDirIteratorPrivate::QDirIteratorPrivate(const QString &path, const QStringList &nameFilters, + QDir::Filters filters, QDirIterator::IteratorFlags flags) + : engine(0), path(path), iteratorFlags(flags), followNextDir(false), first(true), done(false) +{ + if (filters == QDir::NoFilter) + filters = QDir::AllEntries; + this->filters = filters; + this->nameFilters = nameFilters; + + fileInfo.setFile(path); + pushSubDirectory(fileInfo.isSymLink() ? fileInfo.canonicalFilePath() : path, + nameFilters, filters); +} + +/*! + \internal +*/ +QDirIteratorPrivate::~QDirIteratorPrivate() +{ + delete engine; +} + +/*! + \internal +*/ +void QDirIteratorPrivate::pushSubDirectory(const QString &path, const QStringList &nameFilters, + QDir::Filters filters) +{ + if (iteratorFlags & QDirIterator::FollowSymlinks) { + if (fileInfo.filePath() != path) + fileInfo.setFile(path); + if (fileInfo.isSymLink()) { + visitedLinks << fileInfo.canonicalFilePath(); + } else { + visitedLinks << fileInfo.absoluteFilePath(); + } + } + + if (engine || (engine = QAbstractFileEngine::create(this->path))) { + engine->setFileName(path); + QAbstractFileEngineIterator *it = engine->beginEntryList(filters, nameFilters); + if (it) { + it->setPath(path); + fileEngineIterators << it; + } else { + // No iterator; no entry list. + } + } +} + +/*! + \internal +*/ +void QDirIteratorPrivate::advance() +{ + // Store the current entry + if (!fileEngineIterators.isEmpty()) + currentFilePath = fileEngineIterators.top()->currentFilePath(); + + // Advance to the next entry + if (followNextDir) { + // Start by navigating into the current directory. + followNextDir = false; + + QAbstractFileEngineIterator *it = fileEngineIterators.top(); + + QString subDir = it->currentFilePath(); +#ifdef Q_OS_WIN + if (fileInfo.isSymLink()) + subDir = fileInfo.canonicalFilePath(); +#endif + pushSubDirectory(subDir, it->nameFilters(), it->filters()); + } + + while (!fileEngineIterators.isEmpty()) { + QAbstractFileEngineIterator *it = fileEngineIterators.top(); + + // Find the next valid iterator that matches the filters. + bool foundDirectory = false; + while (it->hasNext()) { + it->next(); + if (matchesFilters(it)) { + fileInfo = it->currentFileInfo(); + // Signal that we want to follow this entry. + followNextDir = shouldFollowDirectory(fileInfo); + + //We found a matching entry. + return; + + } else if (iteratorFlags & QDirIterator::Subdirectories) { + QFileInfo fileInfo = it->currentFileInfo(); + if (!shouldFollowDirectory(fileInfo)) + continue; + QString subDir = it->currentFilePath(); +#ifdef Q_OS_WIN + if (fileInfo.isSymLink()) + subDir = fileInfo.canonicalFilePath(); +#endif + pushSubDirectory(subDir, it->nameFilters(), it->filters()); + + foundDirectory = true; + break; + } + } + if (!foundDirectory) + delete fileEngineIterators.pop(); + } + done = true; +} + +/*! + \internal + */ +bool QDirIteratorPrivate::shouldFollowDirectory(const QFileInfo &fileInfo) +{ + // If we're doing flat iteration, we're done. + if (!(iteratorFlags & QDirIterator::Subdirectories)) + return false; + + // Never follow non-directory entries + if (!fileInfo.isDir()) + return false; + + + // Never follow . and .. + if (fileInfo.fileName() == QLatin1String(".") || fileInfo.fileName() == QLatin1String("..")) + return false; + + + // Check symlinks + if (fileInfo.isSymLink() && !(iteratorFlags & QDirIterator::FollowSymlinks)) { + // Follow symlinks only if FollowSymlinks was passed + return false; + } + + // Stop link loops + if (visitedLinks.contains(fileInfo.canonicalFilePath())) + return false; + + return true; +} + + +/*! + \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 QAbstractFileEngineIterator *it) const +{ + const bool filterPermissions = ((filters & QDir::PermissionMask) + && (filters & QDir::PermissionMask) != QDir::PermissionMask); + const bool skipDirs = !(filters & (QDir::Dirs | QDir::AllDirs)); + const bool skipFiles = !(filters & QDir::Files); + const bool skipSymlinks = (filters & QDir::NoSymLinks); + const bool doReadable = !filterPermissions || (filters & QDir::Readable); + const bool doWritable = !filterPermissions || (filters & QDir::Writable); + const bool doExecutable = !filterPermissions || (filters & QDir::Executable); + const bool includeHidden = (filters & QDir::Hidden); + const bool includeSystem = (filters & QDir::System); + +#ifndef QT_NO_REGEXP + // Prepare name filters + QList<QRegExp> regexps; + bool hasNameFilters = !nameFilters.isEmpty() && !(nameFilters.contains(QLatin1String("*"))); + if (hasNameFilters) { + for (int i = 0; i < nameFilters.size(); ++i) { + regexps << QRegExp(nameFilters.at(i), + (filters & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive, + QRegExp::Wildcard); + } + } +#endif + + QString fileName = it->currentFileName(); + if (fileName.isEmpty()) { + // invalid entry + return false; + } + + QFileInfo fi = it->currentFileInfo(); + QString filePath = it->currentFilePath(); + +#ifndef QT_NO_REGEXP + // Pass all entries through name filters, except dirs if the AllDirs + // filter is passed. + if (hasNameFilters && !((filters & QDir::AllDirs) && fi.isDir())) { + bool matched = false; + for (int i = 0; i < regexps.size(); ++i) { + if (regexps.at(i).exactMatch(fileName)) { + matched = true; + break; + } + } + if (!matched) + return false; + } +#endif + + bool dotOrDotDot = (fileName == QLatin1String(".") || fileName == QLatin1String("..")); + if ((filters & QDir::NoDotAndDotDot) && dotOrDotDot) + return false; + + bool isHidden = !dotOrDotDot && fi.isHidden(); + if (!includeHidden && isHidden) + return false; + + bool isSystem = (!fi.isFile() && !fi.isDir() && !fi.isSymLink()) + || (!fi.exists() && fi.isSymLink()); + if (!includeSystem && isSystem) + return false; + + bool alwaysShow = (filters & QDir::TypeMask) == 0 + && ((isHidden && includeHidden) + || (includeSystem && isSystem)); + + // Skip files and directories + if ((filters & QDir::AllDirs) == 0 && skipDirs && fi.isDir()) { + if (!alwaysShow) + return false; + } + + if ((skipFiles && (fi.isFile() || !fi.exists())) + || (skipSymlinks && fi.isSymLink())) { + if (!alwaysShow) + return false; + } + + if (filterPermissions + && ((doReadable && !fi.isReadable()) + || (doWritable && !fi.isWritable()) + || (doExecutable && !fi.isExecutable()))) { + return false; + } + + if (!includeSystem && !dotOrDotDot && ((fi.exists() && !fi.isFile() && !fi.isDir() && !fi.isSymLink()) + || (!fi.exists() && fi.isSymLink()))) { + 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) + : d(new QDirIteratorPrivate(dir.path(), dir.nameFilters(), dir.filter(), flags)) +{ + d->q = this; +} + +/*! + 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. + + \warning This constructor expects \a flags to be left at its default value. Use + the constructors that do not take the \a filters argument instead. + + \sa hasNext(), next(), IteratorFlags +*/ +QDirIterator::QDirIterator(const QString &path, QDir::Filters filters, IteratorFlags flags) + : d(new QDirIteratorPrivate(path, QStringList(QLatin1String("*")), filters, flags)) +{ + d->q = this; +} + +/*! + 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(path, QStringList(QLatin1String("*")), QDir::NoFilter, flags)) +{ + d->q = this; +} + +/*! + 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. + + \warning This constructor expects \c flags to be left at its default value. Use the + constructors that do not take the \a filters argument instead. + + \sa hasNext(), next(), IteratorFlags +*/ +QDirIterator::QDirIterator(const QString &path, const QStringList &nameFilters, + QDir::Filters filters, IteratorFlags flags) + : d(new QDirIteratorPrivate(path, nameFilters, filters, flags)) +{ + d->q = this; +} + +/*! + Destroys the QDirIterator. +*/ +QDirIterator::~QDirIterator() +{ + qDeleteAll(d->fileEngineIterators); + delete d; +} + +/*! + 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() +{ + if (!hasNext()) + return QString(); + 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->first) { + d->first = false; + d->advance(); + } + return !d->done; +} + +/*! + Returns the file name for the current directory entry, without the path + prepended. If the current entry is invalid (i.e., isValid() returns + false), a null QString is returned. + + This function is provided for the convenience when iterating single + directories. For recursive iteration, you should call filePath() or + fileInfo() instead. + + \sa filePath(), fileInfo() +*/ +QString QDirIterator::fileName() const +{ + if (d->fileInfo.path() != d->currentFilePath) + d->fileInfo.setFile(d->currentFilePath); + return d->fileInfo.fileName(); +} + +/*! + Returns the full file path for the current directory entry. If the current + entry is invalid (i.e., isValid() returns false), a null QString is + returned. + + \sa fileInfo(), fileName() +*/ +QString QDirIterator::filePath() const +{ + return d->currentFilePath; +} + +/*! + Returns a QFileInfo for the current directory entry. If the current entry + is invalid (i.e., isValid() returns false), a null QFileInfo is returned. + + \sa filePath(), fileName() +*/ +QFileInfo QDirIterator::fileInfo() const +{ + if (d->fileInfo.filePath() != d->currentFilePath) + d->fileInfo.setFile(d->currentFilePath); + return d->fileInfo; +} + +/*! + Returns the base directory of the iterator. +*/ +QString QDirIterator::path() const +{ + return d->path; +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qdiriterator.h b/src/corelib/io/qdiriterator.h new file mode 100644 index 0000000..540ad4f --- /dev/null +++ b/src/corelib/io/qdiriterator.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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) + + 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 0000000..8926bf3 --- /dev/null +++ b/src/corelib/io/qfile.cpp @@ -0,0 +1,1641 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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" +#if defined(QT_BUILD_CORE_LIB) +# include "qcoreapplication.h" +#endif + +#if !defined(Q_OS_WINCE) +#include <errno.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) +{ +#ifndef Q_OS_DARWIN + return f.toLocal8Bit(); +#else + // Mac always expects UTF-8... and decomposed... + return f.normalized(QString::NormalizationForm_D).toUtf8(); +#endif +} + +static QString locale_decode(const QByteArray &f) +{ +#ifndef Q_OS_DARWIN + return QString::fromLocal8Bit(f); +#else + // Mac always gives us UTF-8 and decomposed, we want that composed... + return QString::fromUtf8(f).normalized(QString::NormalizationForm_C); +#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) +{ +} + +QFilePrivate::~QFilePrivate() +{ + delete fileEngine; + fileEngine = 0; +} + +bool +QFilePrivate::openExternalFile(int flags, int fd) +{ +#ifdef QT_NO_FSFILEENGINE + Q_UNUSED(flags); + Q_UNUSED(fd); + return false; +#else + delete fileEngine; + QFSFileEngine *fe = new QFSFileEngine; + fe->setFileName(fileName); + fileEngine = fe; + return fe->open(QIODevice::OpenMode(flags), fd); +#endif +} + +bool +QFilePrivate::openExternalFile(int flags, FILE *fh) +{ +#ifdef QT_NO_FSFILEENGINE + Q_UNUSED(flags); + Q_UNUSED(fh); + return false; +#else + delete fileEngine; + QFSFileEngine *fe = new QFSFileEngine; + fe->setFileName(fileName); + fileEngine = fe; + return fe->open(QIODevice::OpenMode(flags), fh); +#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) +{ + Q_Q(QFile); + error = err; + q->setErrorString(errStr); +} + +void +QFilePrivate::setError(QFile::FileError err, int errNum) +{ + Q_Q(QFile); + error = err; + q->setErrorString(qt_error_string(errNum)); +} + +//************* QFile + +/*! + \class QFile + \brief The QFile class provides an interface for reading from and writing to files. + + \ingroup io + \mainclass + \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 +*/ + +#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(); +#ifdef QT_NO_QOBJECT + delete d_ptr; +#endif +} + +/*! + 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; + } + close(); + if(error() == QFile::NoError) { + if(fileEngine()->remove()) { + unsetError(); + return true; + } +#if defined(Q_OS_WIN) + d->setError(QFile::RemoveError, GetLastError()); +#else + d->setError(QFile::RemoveError, errno); +#endif + } + 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; + } + close(); + if(error() == QFile::NoError) { + if (fileEngine()->rename(newName)) { + unsetError(); + return true; + } + + QFile in(fileName()); + QFile out(newName); + if (in.open(QIODevice::ReadOnly)) { + if (out.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + bool error = false; + char block[4096]; + qint64 read; + while ((read = in.read(block, sizeof(block))) > 0) { + if (read != out.write(block, read)) { + d->setError(QFile::RenameError, out.errorString()); + error = true; + break; + } + } + if (read == -1) { + d->setError(QFile::RenameError, in.errorString()); + return true; + } + if(!error) + in.remove(); + return !error; + } + } + d->setError(QFile::RenameError, out.isOpen() ? in.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, errno); + 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; + } + 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)); + } +#ifndef QT_NO_TEMPORARYFILE + if (!error) + out.setAutoRemove(false); +#endif + } + } + 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. + + \note Because of limitations in the native API, QFile ignores the + Unbuffered flag on Windows. + + \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; + } + if (fileEngine()->open(mode)) { + QIODevice::open(mode); + if (mode & Append) + seek(size()); + return true; + } + QFile::FileError err = fileEngine()->error(); + if(err == QFile::UnspecifiedError) + err = QFile::OpenError; + d->setError(err, 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 is \c stdin, \c stdout, or \c stderr, you may not be able + to seek(). See QIODevice::isSequentialAccess() 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 seek() and resize(). + Also, size() is set to \c 0. + + \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) +{ + 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)) { + QIODevice::open(mode); + if (mode & Append) { + seek(size()); + } else { + long pos = 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 descripter \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 0 (\c stdin), 1 (\c stdout), or 2 (\c + stderr), you may not be able to seek(). size() is set to \c + LLONG_MAX (in \c <climits>). + + \warning For Windows CE you may not be able to call seek(), setSize(), + fileTime(). size() is set to \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) +{ + 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)) { + QIODevice::open(mode); + if (mode & Append) + seek(size()); + return true; + } + return false; +} + +/*! + 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 +{ + if (!isOpen()) + return -1; + + if (QAbstractFileEngine *engine = fileEngine()) + return engine->handle(); + return -1; +} + +/*! + \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); + QAbstractFileEngine *engine = fileEngine(); + if (engine + && engine->supportsExtension(QAbstractFileEngine::MapExtension)) { + unsetError(); + uchar *address = engine->map(offset, size, flags); + if (address == 0) + d->setError(engine->error(), engine->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); + QAbstractFileEngine *engine = fileEngine(); + if (engine + && engine->supportsExtension(QAbstractFileEngine::UnMapExtension)) { + unsetError(); + bool success = engine->unmap(address); + if (!success) + d->setError(engine->error(), engine->errorString()); + return success; + } + 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; + if (isOpen() && fileEngine()->pos() > sz) + seek(sz); + if(fileEngine()->setSize(sz)) { + unsetError(); + return true; + } + d->setError(QFile::ResizeError, errno); + 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, errno); + 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->size()); + 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->writeBuffer.isEmpty()) { + qint64 size = d->writeBuffer.size(); + if (_qfile_writeData(d->fileEngine ? d->fileEngine : fileEngine(), + &d->writeBuffer) != size) { + QFile::FileError err = fileEngine()->error(); + if(err == QFile::UnspecifiedError) + err = QFile::WriteError; + d->setError(err, fileEngine()->errorString()); + return false; + } + } + + if (!fileEngine()->flush()) { + QFile::FileError err = fileEngine()->error(); + if(err == QFile::UnspecifiedError) + err = QFile::WriteError; + d->setError(err, fileEngine()->errorString()); + return false; + } + return true; +} + +/*! + Flushes the file and then closes it. + + \sa QIODevice::close() +*/ +void +QFile::close() +{ + Q_D(QFile); + if(!isOpen()) + return; + flush(); + QIODevice::close(); + + unsetError(); + if(!fileEngine()->close()) + d->setError(fileEngine()->error(), 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; + return fileEngine()->size(); +} + +/*! + \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 (!isOpen()) + return true; + + if (!d->ensureFlushed()) + return false; + + // If there's buffered data left, we're not at the end. + if (!d->buffer.isEmpty()) + return false; + + // If the file engine knows best, say what it says. + if (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 fileEngine()->atEnd(); + } + + // Fall back to checking how much is available (will stat files). + return bytesAvailable() == 0; +} + +/*! + \reimp +*/ + +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 (!fileEngine()->seek(off) || !QIODevice::seek(off)) { + QFile::FileError err = fileEngine()->error(); + if(err == QFile::UnspecifiedError) + err = QFile::PositionError; + d->setError(err, fileEngine()->errorString()); + return false; + } + d->error = NoError; + return true; +} + +/*! + \reimp +*/ +qint64 QFile::readLineData(char *data, qint64 maxlen) +{ + Q_D(QFile); + if (!d->ensureFlushed()) + return -1; + + if (fileEngine()->supportsExtension(QAbstractFileEngine::FastReadLineExtension)) + return fileEngine()->readLine(data, maxlen); + + // Fall back to QIODevice's readLine implementation if the engine + // cannot do it faster. + return QIODevice::readLineData(data, maxlen); +} + +/*! + \reimp +*/ + +qint64 QFile::readData(char *data, qint64 len) +{ + Q_D(QFile); + d->error = NoError; + if (!d->ensureFlushed()) + return -1; + + qint64 ret = -1; + qint64 read = fileEngine()->read(data, len); + if (read != -1) + ret = read; + + if(ret < 0) { + QFile::FileError err = fileEngine()->error(); + if(err == QFile::UnspecifiedError) + err = QFile::ReadError; + d->setError(err, fileEngine()->errorString()); + } + return ret; +} + +/*! + \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); + d->error = NoError; + 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) { + QAbstractFileEngine *fe = d->fileEngine ? d->fileEngine : fileEngine(); + qint64 ret = fe->write(data, len); + if(ret < 0) { + QFile::FileError err = fileEngine()->error(); + if(err == QFile::UnspecifiedError) + err = QFile::WriteError; + d->setError(err, 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 0000000..e4ca9cc --- /dev/null +++ b/src/corelib/io/qfile.h @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILE_H +#define QFILE_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qstring.h> +#include <stdio.h> + +#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) + + 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); + 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 0000000..ee6f56a --- /dev/null +++ b/src/corelib/io/qfile_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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); + bool openExternalFile(int flags, FILE *fh); + + QString fileName; + mutable QAbstractFileEngine *fileEngine; + bool isOpen; + + 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); + +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 0000000..96e0f82 --- /dev/null +++ b/src/corelib/io/qfileinfo.cpp @@ -0,0 +1,1427 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qfileinfo.h" +#include "qdatetime.h" +#include "qabstractfileengine.h" +#include "qfsfileengine_p.h" +#include "qglobal.h" +#include "qatomic.h" +#include "qhash.h" +#include "qdir.h" + +QT_BEGIN_NAMESPACE + +class QFileInfoPrivate +{ +public: + QFileInfoPrivate(const QFileInfo *copy=0); + ~QFileInfoPrivate(); + + void initFileEngine(const QString &); + + enum Access { + ReadAccess, + WriteAccess, + ExecuteAccess + }; + bool hasAccess(Access access) const; + + uint getFileFlags(QAbstractFileEngine::FileFlags) const; + QDateTime &getFileTime(QAbstractFileEngine::FileTime) const; + QString getFileName(QAbstractFileEngine::FileName) const; + + enum { CachedFileFlags=0x01, CachedLinkTypeFlag=0x02, CachedBundleTypeFlag=0x04, + CachedMTime=0x10, CachedCTime=0x20, CachedATime=0x40, + CachedSize =0x08 }; + struct Data { + inline Data() + : ref(1), fileEngine(0), cache_enabled(1) + { clear(); } + inline Data(const Data ©) + : ref(1), fileEngine(QAbstractFileEngine::create(copy.fileName)), + fileName(copy.fileName), cache_enabled(copy.cache_enabled) + { clear(); } + inline ~Data() { delete fileEngine; } + inline void clearFlags() { + fileFlags = 0; + cachedFlags = 0; + if (fileEngine) + (void)fileEngine->fileFlags(QFSFileEngine::Refresh); + } + inline void clear() { + fileNames.clear(); + clearFlags(); + } + mutable QAtomicInt ref; + + QAbstractFileEngine *fileEngine; + mutable QString fileName; + mutable QHash<int, QString> fileNames; + + mutable uint cachedFlags : 31; + mutable uint 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) + { if (cache_enabled) cachedFlags |= c; } + } *data; + inline void reset() { + detach(); + data->clear(); + } + void detach(); +}; + +QFileInfoPrivate::QFileInfoPrivate(const QFileInfo *copy) +{ + if(copy) { + copy->d_func()->data->ref.ref(); + data = copy->d_func()->data; + } else { + data = new QFileInfoPrivate::Data; + data->clear(); + } +} + +QFileInfoPrivate::~QFileInfoPrivate() +{ + if (!data->ref.deref()) + delete data; + data = 0; +} + +void QFileInfoPrivate::initFileEngine(const QString &file) +{ + detach(); + delete data->fileEngine; + data->fileEngine = 0; + data->clear(); + data->fileEngine = QAbstractFileEngine::create(file); + data->fileName = file; +} + +bool QFileInfoPrivate::hasAccess(Access access) const +{ + if (!(getFileFlags(QAbstractFileEngine::FileInfoAll) & QAbstractFileEngine::LocalDiskFlag)) { + switch (access) { + case ReadAccess: + return getFileFlags(QAbstractFileEngine::ReadUserPerm); + case WriteAccess: + return getFileFlags(QAbstractFileEngine::WriteUserPerm); + case ExecuteAccess: + return getFileFlags(QAbstractFileEngine::ExeUserPerm); + default: + return false; + } + } + + int mode = 0; + switch (access) { + case ReadAccess: + mode = R_OK; + break; + case WriteAccess: + mode = W_OK; + break; + case ExecuteAccess: + mode = X_OK; + break; + }; +#ifdef Q_OS_UNIX + return QT_ACCESS(QFile::encodeName(data->fileName).data(), mode) == 0; +#endif +#ifdef Q_OS_WIN + if ((access == ReadAccess && !getFileFlags(QAbstractFileEngine::ReadUserPerm)) + || (access == WriteAccess && !getFileFlags(QAbstractFileEngine::WriteUserPerm))) { + return false; + } + if (access == ExecuteAccess) + return getFileFlags(QAbstractFileEngine::ExeUserPerm); + + QT_WA( { + return ::_waccess((TCHAR *)QFSFileEnginePrivate::longFileName(data->fileName).utf16(), mode) == 0; + } , { + return QT_ACCESS(QFSFileEnginePrivate::win95Name(data->fileName), mode) == 0; + } ); +#endif + return false; +} + +void QFileInfoPrivate::detach() +{ + qAtomicDetach(data); +} + +QString QFileInfoPrivate::getFileName(QAbstractFileEngine::FileName name) const +{ + if(data->cache_enabled && data->fileNames.contains((int)name)) + return data->fileNames.value(name); + QString ret = data->fileEngine->fileName(name); + if(data->cache_enabled) + data->fileNames.insert((int)name, ret); + return ret; +} + +uint QFileInfoPrivate::getFileFlags(QAbstractFileEngine::FileFlags request) const +{ + // We split the testing into tests for for LinkType, BundleType and the rest. + // 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 flags; + if (!data->getCachedFlag(CachedFileFlags)) { + QAbstractFileEngine::FileFlags req = QAbstractFileEngine::FileInfoAll; + req &= (~QAbstractFileEngine::LinkType); + req &= (~QAbstractFileEngine::BundleType); + + flags = data->fileEngine->fileFlags(req); + data->setCachedFlag(CachedFileFlags); + data->fileFlags |= uint(flags); + } else { + flags = QAbstractFileEngine::FileFlags(data->fileFlags & request); + } + + if (request & QAbstractFileEngine::LinkType) { + if (!data->getCachedFlag(CachedLinkTypeFlag)) { + QAbstractFileEngine::FileFlags linkflag; + linkflag = data->fileEngine->fileFlags(QAbstractFileEngine::LinkType); + + data->setCachedFlag(CachedLinkTypeFlag); + data->fileFlags |= uint(linkflag); + flags |= linkflag; + } + } + + if (request & QAbstractFileEngine::BundleType) { + if (!data->getCachedFlag(CachedBundleTypeFlag)) { + QAbstractFileEngine::FileFlags bundleflag; + bundleflag = data->fileEngine->fileFlags(QAbstractFileEngine::BundleType); + + data->setCachedFlag(CachedBundleTypeFlag); + data->fileFlags |= uint(bundleflag); + flags |= bundleflag; + } + } + + // no else branch + // if we had it cached, it was caught in the previous else branch + + return flags & request; +} + +QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request) const +{ + if (!data->cache_enabled) + data->clearFlags(); + if(request == QAbstractFileEngine::CreationTime) { + if(data->getCachedFlag(CachedCTime)) + return data->fileTimes[request]; + data->setCachedFlag(CachedCTime); + return (data->fileTimes[request] = data->fileEngine->fileTime(request)); + } + if(request == QAbstractFileEngine::ModificationTime) { + if(data->getCachedFlag(CachedMTime)) + return data->fileTimes[request]; + data->setCachedFlag(CachedMTime); + return (data->fileTimes[request] = data->fileEngine->fileTime(request)); + } + if(request == QAbstractFileEngine::AccessTime) { + if(data->getCachedFlag(CachedATime)) + return data->fileTimes[request]; + data->setCachedFlag(CachedATime); + return (data->fileTimes[request] = data->fileEngine->fileTime(request)); + } + return data->fileTimes[0]; //cannot really happen +} + +//************* 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 +*/ + +/*! + 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()) +{ + d_ptr->initFileEngine(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()) +{ + d_ptr->initFileEngine(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()) +{ + d_ptr->initFileEngine(dir.filePath(file)); +} + +/*! + Constructs a new QFileInfo that is a copy of the given \a fileinfo. +*/ + +QFileInfo::QFileInfo(const QFileInfo &fileinfo) : d_ptr(new QFileInfoPrivate(&fileinfo)) +{ + +} + +/*! + Destroys the QFileInfo and frees its resources. +*/ + + +QFileInfo::~QFileInfo() +{ + delete d_ptr; + d_ptr = 0; +} + +/*! + \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_func()->data == d->data) + return true; + if(!d->data->fileEngine || !fileinfo.d_func()->data->fileEngine) + return false; + if(d->data->fileEngine->caseSensitive() != fileinfo.d_func()->data->fileEngine->caseSensitive()) + return false; + if(fileinfo.size() == size()) { //if the size isn't the same... + QString file1 = canonicalFilePath(), + file2 = fileinfo.canonicalFilePath(); + if(file1.length() == file2.length()) { + if(!fileinfo.d_func()->data->fileEngine->caseSensitive()) { + for(int i = 0; i < file1.length(); i++) { + if(file1.at(i).toLower() != file2.at(i).toLower()) + return false; + } + return true; + } + return (file1 == file2); + } + } + return false; +} + +/*! + 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) +{ + Q_D(QFileInfo); + qAtomicAssign(d->data, fileinfo.d_func()->data); + 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) +{ + Q_D(QFileInfo); + d->initFileEngine(file); +} + +/*! + \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) +{ + Q_D(QFileInfo); + d->initFileEngine(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) +{ + Q_D(QFileInfo); + d->initFileEngine(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->data->fileEngine) + 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->data->fileEngine) + 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->data->fileEngine) + 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->data->fileEngine) + 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->data->fileEngine) + return QLatin1String(""); + return d->getFileName(QAbstractFileEngine::PathName); +} + +/*! + \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->data->fileEngine) + return true; + return d->data->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() +{ + Q_D(QFileInfo); + if(!d->data->fileEngine || !d->data->fileEngine->isRelativePath()) + return false; + QString absFileName = d->getFileName(QAbstractFileEngine::AbsoluteName); + d->initFileEngine(absFileName); + 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->data->fileEngine) + return false; + 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->reset(); +} + +/*! + 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->data->fileEngine) + return QLatin1String(""); + return d->getFileName(QAbstractFileEngine::DefaultName); +} + +/*! + 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->data->fileEngine) + return QLatin1String(""); + return d->getFileName(QAbstractFileEngine::BaseName); +} + +/*! + \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->data->fileEngine) + 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->data->fileEngine) + return QLatin1String(""); + return d->getFileName(QAbstractFileEngine::BaseName).section(QLatin1Char('.'), 0, 0); +} + +/*! + 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->data->fileEngine) + return QLatin1String(""); + QString name = d->getFileName(QAbstractFileEngine::BaseName); + int index = name.lastIndexOf(QLatin1Char('.')); + return (index == -1) ? name : name.left(index); +} + +/*! + 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->data->fileEngine) + return QLatin1String(""); + QString fileName = d->getFileName(QAbstractFileEngine::BaseName); + int firstDot = fileName.indexOf(QLatin1Char('.')); + if (firstDot == -1) + return QLatin1String(""); + return fileName.mid(firstDot + 1); +} + +/*! + 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->data->fileEngine) + return QLatin1String(""); + QString fileName = d->getFileName(QAbstractFileEngine::BaseName); + int lastDot = fileName.lastIndexOf(QLatin1Char('.')); + if (lastDot == -1) + return QLatin1String(""); + return fileName.mid(lastDot + 1); +} + + +/*! + 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 follwing, dir() returns a QDir for + \c{"~/examples/191697"}. + + \snippet doc/src/snippets/fileinfo/main.cpp 0 + + For each of the follwing, dir() returns a QDir for + \c{"."}. + + \snippet doc/src/snippets/fileinfo/main.cpp 1 + + \sa absolutePath(), filePath(), fileName(), isRelative(), absoluteDir() +*/ + +QDir QFileInfo::dir() const +{ + // ### Qt5: Maybe rename this to parentDirectory(), considering what it actually do? + return QDir(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->data->fileEngine) + return false; + return d->hasAccess(QFileInfoPrivate::ReadAccess); +} + +/*! + 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->data->fileEngine) + return false; + return d->hasAccess(QFileInfoPrivate::WriteAccess); +} + +/*! + Returns true if the file is executable; otherwise returns false. + + \sa isReadable(), isWritable(), permission() +*/ + +bool QFileInfo::isExecutable() const +{ + Q_D(const QFileInfo); + if(!d->data->fileEngine) + return false; + return d->hasAccess(QFileInfoPrivate::ExecuteAccess); +} + +/*! + 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->data->fileEngine) + return false; + 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->data->fileEngine) + return false; + 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->data->fileEngine) + return false; + 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->data->fileEngine) + return false; + 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->data->fileEngine) + return false; + 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->data->fileEngine) + return true; + 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->data->fileEngine) + 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->data->fileEngine) + return QLatin1String(""); + return d->data->fileEngine->owner(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->data->fileEngine) + return 0; + return d->data->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->data->fileEngine) + return QLatin1String(""); + return d->data->fileEngine->owner(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->data->fileEngine) + return 0; + return d->data->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->data->fileEngine) + return false; + 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->data->fileEngine) + return 0; + 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->data->fileEngine) + return 0; + if(!d->data->getCachedFlag(QFileInfoPrivate::CachedSize)) { + d->data->setCachedFlag(QFileInfoPrivate::CachedSize); + d->data->fileSize = d->data->fileEngine->size(); + } + return d->data->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->data->fileEngine) + return QDateTime(); + 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->data->fileEngine) + return QDateTime(); + 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->data->fileEngine) + return QDateTime(); + return d->getFileTime(QAbstractFileEngine::AccessTime); +} + +/*! \internal + Detaches all internal data. +*/ + +void QFileInfo::detach() +{ + Q_D(QFileInfo); + d->detach(); +} + +/*! + Returns true if caching is enabled; otherwise returns false. + + \sa setCaching(), refresh() +*/ + +bool QFileInfo::caching() const +{ + Q_D(const QFileInfo); + return d->data->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); + detach(); + d->data->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 0000000..97997a3 --- /dev/null +++ b/src/corelib/io/qfileinfo.h @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEINFO_H +#define QFILEINFO_H + +#include <QtCore/qfile.h> +#include <QtCore/qlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QDir; +class QDateTime; +class QFileInfoPrivate; + +class Q_CORE_EXPORT QFileInfo +{ +public: + 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); + 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: + QFileInfoPrivate *d_ptr; +private: + Q_DECLARE_PRIVATE(QFileInfo) +}; +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 0000000..7d66581 --- /dev/null +++ b/src/corelib/io/qfileinfo_p.h @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 for the convenience +// of QIODevice. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qfileinfo.h" + +QT_BEGIN_NAMESPACE + +class QFileInfoPrivate +{ +public: + QFileInfoPrivate(const QFileInfo *copy=0); + ~QFileInfoPrivate(); + + void initFileEngine(const QString &); + + enum Access { + ReadAccess, + WriteAccess, + ExecuteAccess + }; + bool hasAccess(Access access) const; + + uint getFileFlags(QAbstractFileEngine::FileFlags) const; + QDateTime &getFileTime(QAbstractFileEngine::FileTime) const; + QString getFileName(QAbstractFileEngine::FileName) const; + + enum { + CachedFileFlags = 0x01, + CachedLinkTypeFlag = 0x02, + CachedBundleTypeFlag= 0x04, + CachedMTime = 0x10, + CachedCTime = 0x20, + CachedATime = 0x40, + CachedSize = 0x08 + }; + + struct Data + { + inline Data() + : ref(1), fileEngine(0), cache_enabled(1) + { + clear(); + } + + inline Data(const Data ©) + : ref(1), fileEngine(QAbstractFileEngine::create(copy.fileName)), + fileName(copy.fileName), cache_enabled(copy.cache_enabled) + { + clear(); + } + + inline ~Data() + { + delete fileEngine; + } + + inline void clear() + { + fileNames.clear(); + fileFlags = 0; + cachedFlags = 0; + } + + mutable QAtomicInt ref; + + QAbstractFileEngine *fileEngine; + mutable QString fileName; + mutable QHash<int, QString> fileNames; + mutable uint cachedFlags : 31; + mutable uint 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) + { if (cache_enabled) cachedFlags |= c; } + } *data; + + inline void reset() { + detach(); + data->clear(); + } + + void detach(); +}; + + +QT_END_NAMESPACE +#endif + diff --git a/src/corelib/io/qfilesystemwatcher.cpp b/src/corelib/io/qfilesystemwatcher.cpp new file mode 100644 index 0000000..b83ab85 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher.cpp @@ -0,0 +1,624 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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) +# 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 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() +{ + QMetaObject::invokeMethod(this, "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) { + 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) + 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 and all BSD variants, for + example, an open file descriptor is required for each monitored + file. The 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. + + \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 0000000..b9cea53 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 0000000..e87375a --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_dnotify.cpp @@ -0,0 +1,461 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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> + +#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) +{ + ::write(qfswd_fileChanged_pipe[1], &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); + + ::pipe(qfswd_fileChanged_pipe); + ::fcntl(qfswd_fileChanged_pipe[0], F_SETFL, + ::fcntl(qfswd_fileChanged_pipe[0], F_GETFL) | 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 = ::read(qfswd_fileChanged_pipe[0], &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) { + ::close(iter->fd); + if(iter->parentFd) + ::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) { + + DIR *d = ::opendir(path.toUtf8().constData()); + if(!d) continue; // Could not open directory + DIR *parent = 0; + + QDir parentDir(path); + if(!parentDir.isRoot()) { + parentDir.cdUp(); + parent = ::opendir(parentDir.path().toUtf8().constData()); + if(!parent) { + ::closedir(d); + continue; + } + } + + fd = ::dirfd(d); + int parentFd = parent?::dirfd(parent):0; + + 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))) { + + ::closedir(d); + if(parent) ::closedir(parent); + + 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 + ::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()) { + ::close(directory.fd); + if(directory.parentFd) { + ::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 0000000..81a70ed --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_dnotify_p.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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_inotify.cpp b/src/corelib/io/qfilesystemwatcher_inotify.cpp new file mode 100644 index 0000000..4445e3c --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_inotify.cpp @@ -0,0 +1,376 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemwatcher.h" +#include "qfilesystemwatcher_inotify_p.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#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 +#elif defined(__x86_64__) +# define __NR_inotify_init 253 +# define __NR_inotify_add_watch 254 +# define __NR_inotify_rm_watch 255 +#elif defined(__powerpc__) || defined(__powerpc64__) +# define __NR_inotify_init 275 +# define __NR_inotify_add_watch 276 +# define __NR_inotify_rm_watch 277 +#elif defined (__ia64__) +# define __NR_inotify_init 1277 +# define __NR_inotify_add_watch 1278 +# define __NR_inotify_rm_watch 1279 +#elif defined (__s390__) || defined (__s390x__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +#elif defined (__alpha__) +# define __NR_inotify_init 444 +# define __NR_inotify_add_watch 445 +# define __NR_inotify_rm_watch 446 +#elif defined (__sparc__) || defined (__sparc64__) +# define __NR_inotify_init 151 +# define __NR_inotify_add_watch 152 +# define __NR_inotify_rm_watch 156 +#elif defined (__arm__) +# define __NR_inotify_init 316 +# define __NR_inotify_add_watch 317 +# define __NR_inotify_rm_watch 318 +#elif defined (__sh__) +# define __NR_inotify_init 290 +# define __NR_inotify_add_watch 291 +# define __NR_inotify_rm_watch 292 +#elif defined (__sh64__) +# define __NR_inotify_init 318 +# define __NR_inotify_add_watch 319 +# define __NR_inotify_rm_watch 320 +#elif defined (__mips__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +#elif defined (__hppa__) +# define __NR_inotify_init 269 +# define __NR_inotify_add_watch 270 +# define __NR_inotify_rm_watch 271 +#elif defined (__avr32__) +# define __NR_inotify_init 240 +# define __NR_inotify_add_watch 241 +# define __NR_inotify_rm_watch 242 +#elif defined (__mc68000__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +#else +# error "This architecture is not supported. Please talk to qt-bugs@trolltech.com" +#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); +} + +// 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() +{ + int fd = inotify_init(); + if (fd <= 0) + return 0; + return new QInotifyFileSystemWatcherEngine(fd); +} + +QInotifyFileSystemWatcherEngine::QInotifyFileSystemWatcherEngine(int fd) + : inotifyFd(fd) +{ + fcntl(inotifyFd, F_SETFD, FD_CLOEXEC); + + moveToThread(this); +} + +QInotifyFileSystemWatcherEngine::~QInotifyFileSystemWatcherEngine() +{ + foreach (int id, pathToID.values()) + 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() +{ + QMetaObject::invokeMethod(this, "quit"); +} + +void QInotifyFileSystemWatcherEngine::readFromInotify() +{ + QMutexLocker locker(&mutex); + + // qDebug() << "QInotifyFileSystemWatcherEngine::readFromInotify"; + + int buffSize = 0; + ioctl(inotifyFd, FIONREAD, &buffSize); + QVarLengthArray<char, 4096> buffer(buffSize); + buffSize = read(inotifyFd, buffer.data(), buffSize); + const char *at = buffer.data(); + const char * const end = at + buffSize; + + QMap<int, inotify_event> eventForId; + while (at < end) { + const inotify_event *event = reinterpret_cast<const 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; + } + + QMap<int, inotify_event>::const_iterator it = eventForId.constBegin(); + while (it != eventForId.constEnd()) { + 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 0000000..8f3595c --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_inotify_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 0000000..cdf436e --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_kqueue.cpp @@ -0,0 +1,334 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qplatformdefs.h> + +#include "qfilesystemwatcher.h" +#include "qfilesystemwatcher_kqueue_p.h" + +#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.values()) + ::close(id < 0 ? -id : id); +} + +QStringList QKqueueFileSystemWatcherEngine::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(); + int fd; +#if defined(O_EVTONLY) + fd = ::open(QFile::encodeName(path), O_EVTONLY); +#else + fd = ::open(QFile::encodeName(path), O_RDONLY); +#endif + if (fd == -1) { + perror("QKqueueFileSystemWatcherEngine::addPaths: open"); + continue; + } + + 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_ONESHOT, + 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) +{ + 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 fd = id < 0 ? -id : id; + struct kevent kev; + EV_SET(&kev, + fd, + EVFILT_VNODE, + EV_DELETE, + 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::removeWatch: kevent"); + } + ::close(fd); + + it.remove(); + if (id < 0) + directories->removeAll(path); + else + files->removeAll(path); + } + + if (pathToID.isEmpty()) { + stop(); + locker.unlock(); + wait(); + locker.relock(); + } else { + write(kqpipe[1], "@", 1); + } + + return p; +} + +void QKqueueFileSystemWatcherEngine::stop() +{ + write(kqpipe[1], "q", 1); +} + +void QKqueueFileSystemWatcherEngine::run() +{ + static const struct timespec ZeroTimeout = { 0, 0 }; + + forever { + struct kevent kev; + DEBUG() << "QKqueueFileSystemWatcherEngine: waiting for kevents..."; + int r = kevent(kqfd, 0, 0, &kev, 1, 0); + if (r < 0) { + perror("QKqueueFileSystemWatcherEngine: error during kevent wait"); + return; + } + + QMutexLocker locker(&mutex); + do { + int fd = kev.ident; + + DEBUG() << "QKqueueFileSystemWatcherEngine: processing kevent" << kev.ident << kev.filter; + if (fd == kqpipe[0]) { + char c; + if (read(kqpipe[0], &c, 1) != 1) { + perror("QKqueueFileSystemWatcherEngine: error reading from pipe"); + return; + } + switch (c) { + case 'q': + DEBUG() << "QKqueueFileSystemWatcherEngine: thread received 'q', exiting..."; + return; + case '@': + DEBUG() << "QKqueueFileSystemWatcherEngine: thread received '@', continuing..."; + break; + default: + DEBUG() << "QKqueueFileSystemWatcherEngine: thread received unknow message" << c; + break; + } + } else { + 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, re-enabling watch"; + + if (id < 0) + emit directoryChanged(path, false); + else + emit fileChanged(path, false); + + // renable the watch + EV_SET(&kev, + fd, + EVFILT_VNODE, + EV_ADD | EV_ENABLE | EV_ONESHOT, + 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::processKqueueEvents: kevent EV_ADD"); + } + } + } + + // are there any more? + r = kevent(kqfd, 0, 0, &kev, 1, &ZeroTimeout); + } while (r > 0); + } +} + +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 0000000..789fb3a --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_kqueue_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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> + +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 // 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 0000000..7cc9153 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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() + { + moveToThread(this); + } + +public: + // fills \a files and \a directires 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 0000000..883b70f --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_symbian.cpp @@ -0,0 +1,252 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the $MODULE$ of the Qt Toolkit. +** +** $TROLLTECH_DUAL_EMBEDDED_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 + +CNotifyChangeEvent* CNotifyChangeEvent::New(RFs &fs, const TDesC& file, + QSymbianFileSystemWatcherEngine* e) +{ + CNotifyChangeEvent* self = new CNotifyChangeEvent(fs, file, e); + return self; +} + +CNotifyChangeEvent::CNotifyChangeEvent(RFs &fs, const TDesC& file, + QSymbianFileSystemWatcherEngine* e, TInt aPriority) : + CActive(aPriority), + fsSession(fs), + watchedPath(file), + engine(e) +{ + fsSession.NotifyChange(ENotifyAll, iStatus, file); + CActiveScheduler::Add(this); + SetActive(); +} + +CNotifyChangeEvent::~CNotifyChangeEvent() +{ + Cancel(); +} + +void CNotifyChangeEvent::RunL() +{ + if (iStatus.Int() == KErrNone){ + fsSession.NotifyChange(ENotifyAll, iStatus, watchedPath); + SetActive(); + engine->emitPathChanged(this); + } else { + qWarning("CNotifyChangeEvent::RunL() - Failed to order change notifications: %d", iStatus.Int()); + } +} + +void CNotifyChangeEvent::DoCancel() +{ + fsSession.NotifyChangeCancel(); +} + +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; + + if (!startWatcher()) { + qWarning("Could not start QSymbianFileSystemWatcherEngine thread"); + + return p; + } + + 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('/')) { + filePath += QChar('/'); + } + + currentEvent = NULL; + QMetaObject::invokeMethod(this, + "addNativeListener", + Qt::QueuedConnection, + Q_ARG(QString, filePath)); + + syncCondition.wait(&mutex); + + if (currentEvent) { + currentEvent->isDir = isDir; + + activeObjectToPath.insert(currentEvent, 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(); + + currentEvent = activeObjectToPath.key(path); + if (!currentEvent) + continue; + activeObjectToPath.remove(currentEvent); + + QMetaObject::invokeMethod(this, + "removeNativeListener", + Qt::QueuedConnection); + + syncCondition.wait(&mutex); + + it.remove(); + + files->removeAll(path); + directories->removeAll(path); + } + + if (activeObjectToPath.size() == 0) + stop(); + + return p; +} + +void QSymbianFileSystemWatcherEngine::emitPathChanged(CNotifyChangeEvent *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() +{ + QMetaObject::invokeMethod(this, "quit"); + wait(); +} + +// This method must be called inside mutex +bool QSymbianFileSystemWatcherEngine::startWatcher() +{ + bool retval = true; + + if (!watcherStarted) { +#if defined(Q_OS_SYMBIAN) + setStackSize(0x5000); +#endif + start(); + syncCondition.wait(&mutex); + + if (errorCode != KErrNone) { + retval = false; + } else { + watcherStarted = true; + } + } + return retval; +} + + +void QSymbianFileSystemWatcherEngine::run() +{ + // Initialize file session + + errorCode = fsSession.Connect(); + + mutex.lock(); + syncCondition.wakeOne(); + mutex.unlock(); + + if (errorCode == KErrNone) { + exec(); + + foreach(CNotifyChangeEvent* e, activeObjectToPath.keys()) { + e->Cancel(); + delete e; + } + + activeObjectToPath.clear(); + fsSession.Close(); + watcherStarted = false; + } +} + +void QSymbianFileSystemWatcherEngine::addNativeListener(const QString &directoryPath) +{ + QMutexLocker locker(&mutex); + HBufC* buffer = qt_QString2HBufCNewL(QDir::toNativeSeparators(directoryPath)); + currentEvent = CNotifyChangeEvent::New(fsSession, *buffer, this); + delete buffer; + syncCondition.wakeOne(); +} + +void QSymbianFileSystemWatcherEngine::removeNativeListener() +{ + QMutexLocker locker(&mutex); + currentEvent->Cancel(); + delete currentEvent; + currentEvent = 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 0000000..b34418a --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_symbian_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the $MODULE$ of the Qt Toolkit. +** +** $TROLLTECH_DUAL_EMBEDDED_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" +#include "qhash.h" +#include "qmutex.h" +#include "qwaitcondition.h" + +#ifndef QT_NO_FILESYSTEMWATCHER + +#include <e32base.h> +#include <f32file.h> + +QT_BEGIN_NAMESPACE + +class QSymbianFileSystemWatcherEngine; + +class CNotifyChangeEvent : public CActive +{ +public: + CNotifyChangeEvent(RFs &fsSession, const TDesC& file, QSymbianFileSystemWatcherEngine* engine, + TInt aPriority = EPriorityStandard); + ~CNotifyChangeEvent(); + static CNotifyChangeEvent* New(RFs &fsSession, const TDesC& file, + QSymbianFileSystemWatcherEngine* engine); + + bool isDir; + +private: + void RunL(); + void DoCancel(); + + RFs &fsSession; + TPath watchedPath; + QSymbianFileSystemWatcherEngine *engine; +}; + +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 CNotifyChangeEvent; + void emitPathChanged(CNotifyChangeEvent *e); + + bool startWatcher(); + + RFs fsSession; + QHash<CNotifyChangeEvent*, QString> activeObjectToPath; + QMutex mutex; + QWaitCondition syncCondition; + int errorCode; + bool watcherStarted; + CNotifyChangeEvent *currentEvent; +}; + +#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 0000000..74e3e27 --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_win.cpp @@ -0,0 +1,333 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 + +QWindowsFileSystemWatcherEngine::QWindowsFileSystemWatcherEngine() + : msg(0) +{ + HANDLE h = QT_WA_INLINE(CreateEventW(0, false, false, 0), + CreateEventA(0, false, false, 0)); + if (h != INVALID_HANDLE_VALUE) { + handles.reserve(MAXIMUM_WAIT_OBJECTS); + handles.append(h); + } +} + +QWindowsFileSystemWatcherEngine::~QWindowsFileSystemWatcherEngine() +{ + if (handles.isEmpty()) + return; + + stop(); + wait(); + + CloseHandle(handles.at(0)); + handles[0] = INVALID_HANDLE_VALUE; + + foreach (HANDLE h, handles) { + if (h == INVALID_HANDLE_VALUE) + continue; + FindCloseChangeNotification(h); + } +} + +void QWindowsFileSystemWatcherEngine::run() +{ + QMutexLocker locker(&mutex); + forever { + QVector<HANDLE> handlesCopy = handles; + locker.unlock(); + // qDebug() << "QWindowsFileSystemWatcherEngine: 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 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("Acknowledged handle: %d, %p", at, handle); + if (!FindNextChangeNotification(handle)) { + qErrnoWarning("QFileSystemWatcher: FindNextChangeNotification failed"); + } + + QHash<QString, PathInfo> &h = pathInfoForHandle[handle]; + QMutableHashIterator<QString, PathInfo> it(h); + while (it.hasNext()) { + QHash<QString, 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); + } +} + +QStringList QWindowsFileSystemWatcherEngine::addPaths(const QStringList &paths, + QStringList *files, + QStringList *directories) +{ + if (handles.isEmpty() || handles.count() == MAXIMUM_WAIT_OBJECTS) + return paths; + + QMutexLocker locker(&mutex); + + QStringList p = paths; + QMutableListIterator<QString> it(p); + while (it.hasNext()) { + QString path = it.next(); + QString normalPath = path; + if ((normalPath.endsWith(QLatin1Char('/')) || normalPath.endsWith(QLatin1Char('\\'))) +#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; + } + + 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); + + Handle handle = handleForDir.value(absolutePath); + if (handle.handle == INVALID_HANDLE_VALUE || handle.flags != flags) { + // Volume and folder paths need a trailing slash for proper notification + // (e.g. "c:" -> "c:/"). + const QString effectiveAbsolutePath = + isDir ? (absolutePath + QLatin1Char('/')) : absolutePath; + + QT_WA({ + handle.handle = FindFirstChangeNotificationW((TCHAR *) QDir::toNativeSeparators(effectiveAbsolutePath).utf16(), + false, flags); + },{ + handle.handle = FindFirstChangeNotificationA(QDir::toNativeSeparators(effectiveAbsolutePath).toLocal8Bit(), + false, flags); + }) + handle.flags = flags; + if (handle.handle == INVALID_HANDLE_VALUE) + continue; + // qDebug() << "Added handle" << handle << "for" << absolutePath << "to watch" << fileInfo.absoluteFilePath(); + handles.append(handle.handle); + handleForDir.insert(absolutePath, handle); + } + + PathInfo pathInfo; + pathInfo.absolutePath = absolutePath; + pathInfo.isDir = isDir; + pathInfo.path = path; + pathInfo = fileInfo; + QHash<QString, PathInfo> &h = pathInfoForHandle[handle.handle]; + if (!h.contains(fileInfo.absoluteFilePath())) { + pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo); + if (isDir) + directories->append(path); + else + files->append(path); + } + + it.remove(); + } + + if (!isRunning()) { + msg = '@'; + start(); + } else { + wakeup(); + } + + return p; +} + +QStringList QWindowsFileSystemWatcherEngine::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(); + QString normalPath = path; + if (normalPath.endsWith(QLatin1Char('/')) || normalPath.endsWith(QLatin1Char('\\'))) + normalPath.chop(1); + QFileInfo fileInfo(normalPath.toLower()); + + QString absolutePath = fileInfo.absoluteFilePath(); + Handle handle = handleForDir.value(absolutePath); + if (handle.handle == INVALID_HANDLE_VALUE) { + // perhaps path is a file? + absolutePath = fileInfo.absolutePath(); + handle = handleForDir.value(absolutePath); + if (handle.handle == INVALID_HANDLE_VALUE) + continue; + } + + QHash<QString, PathInfo> &h = pathInfoForHandle[handle.handle]; + if (h.remove(fileInfo.absoluteFilePath())) { + // ### + files->removeAll(path); + directories->removeAll(path); + + if (h.isEmpty()) { + // qDebug() << "Closing handle" << handle; + FindCloseChangeNotification(handle.handle); // This one might generate a notification + + int indexOfHandle = handles.indexOf(handle.handle); + Q_ASSERT(indexOfHandle != -1); + handles.remove(indexOfHandle); + + handleForDir.remove(absolutePath); + // h is now invalid + + it.remove(); + } + } + } + + if (handleForDir.isEmpty()) { + stop(); + locker.unlock(); + wait(); + locker.relock(); + } else { + wakeup(); + } + + return p; +} + +void QWindowsFileSystemWatcherEngine::stop() +{ + msg = 'q'; + SetEvent(handles.at(0)); +} + +void QWindowsFileSystemWatcherEngine::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 0000000..525e32f --- /dev/null +++ b/src/corelib/io/qfilesystemwatcher_win_p.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 <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 QWindowsFileSystemWatcherEngine : public QFileSystemWatcherEngine +{ + Q_OBJECT + +public: + QWindowsFileSystemWatcherEngine(); + ~QWindowsFileSystemWatcherEngine(); + + void run(); + + QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories); + QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories); + + void stop(); + +private: + void wakeup(); + + QMutex mutex; + QVector<HANDLE> handles; + int msg; + + class Handle + { + public: + HANDLE handle; + uint flags; + + Handle() + : handle(INVALID_HANDLE_VALUE), flags(0u) + { } + Handle(const Handle &other) + : handle(other.handle), flags(other.flags) + { } + }; + QHash<QString, Handle> handleForDir; + + 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()); + } + }; + QHash<HANDLE, QHash<QString, PathInfo> > pathInfoForHandle; +}; +#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 0000000..61ea7cc --- /dev/null +++ b/src/corelib/io/qfsfileengine.cpp @@ -0,0 +1,873 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfsfileengine_p.h" +#include "qfsfileengine_iterator_p.h" +#include "qdatetime.h" +#include "qdiriterator.h" +#include "qset.h" + +#ifndef QT_NO_FSFILEENGINE + +#if !defined(Q_OS_WINCE) +#include <errno.h> +#endif +#include <stdio.h> + +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; +#ifdef Q_OS_UNIX + need_lstat = 1; + is_link = 0; +#endif + openMode = QIODevice::NotOpen; + fd = -1; + fh = 0; + lastIOCommand = IOFlushCommand; + lastFlushFailed = false; + closeFileHandle = false; +#ifdef Q_OS_WIN + fileAttrib = INVALID_FILE_ATTRIBUTES; + fileHandle = INVALID_HANDLE_VALUE; + cachedFd = -1; +#endif +#ifdef Q_USE_DEPRECATED_MAP_API + fileMapHandle = INVALID_HANDLE_VALUE; +#endif +} + +/*! + \internal + + Returns the canonicalized form of \a path (i.e., with all symlinks + resolved, and all redundant path elements removed. +*/ +QString QFSFileEnginePrivate::canonicalized(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 + // UNC, skip past the first two elements + if (separatorPos == 0 && tmpPath.startsWith(QLatin1String("//"))) + separatorPos = tmpPath.indexOf(slash, 2); + if (separatorPos != -1) +#endif + separatorPos = tmpPath.indexOf(slash, separatorPos + 1); + QString prefix = separatorPos == -1 ? tmpPath : tmpPath.left(separatorPos); + if (!nonSymlinks.contains(prefix)) { + fi.setFile(prefix); + if (fi.isSymLink()) { + QString target = fi.symLinkTarget(); + 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); +} + +/*! + Constructs a QFSFileEngine for the file name \a file. +*/ +QFSFileEngine::QFSFileEngine(const QString &file) : QAbstractFileEngine(*new QFSFileEnginePrivate) +{ + Q_D(QFSFileEngine); + d->filePath = QDir::fromNativeSeparators(file); + d->nativeInitFileName(); +} + +/*! + 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->filePath = QDir::fromNativeSeparators(file); + d->nativeInitFileName(); +} + +/*! + \reimp +*/ +bool QFSFileEngine::open(QIODevice::OpenMode openMode) +{ + Q_D(QFSFileEngine); + if (d->filePath.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) +{ + 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 = false; + d->nativeFilePath.clear(); + d->filePath.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 == -1 && errno == EINTR); + + if (ret == -1) { + q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, + qt_error_string(int(errno))); + 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) +{ + 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->lastFlushFailed = false; + d->closeFileHandle = false; + d->nativeFilePath.clear(); + d->filePath.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))); + 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) + 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; + + // Close the file if we created the handle. + if (closeFileHandle) { + int ret; + do { + 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 +*/ +qint64 QFSFileEnginePrivate::sizeFdFh() const +{ + Q_Q(const QFSFileEngine); + // ### Fix this function, it should not stat unless the file is closed. + QT_STATBUF st; + int ret = 0; + const_cast<QFSFileEngine *>(q)->flush(); + if (fh && nativeFilePath.isEmpty()) { + // Buffered stdlib mode. + // ### This should really be an ftell + ret = QT_FSTAT(QT_FILENO(fh), &st); + } else if (fd == -1) { + // Stateless stat. + ret = QT_STAT(nativeFilePath.constData(), &st); + } else { + // Unbuffered stdio mode. + ret = QT_FSTAT(fd, &st); + } + if (ret == -1) + return 0; + return st.st_size; +} + +/*! + \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 (fh) { + // Buffered stdlib mode. + int ret; + do { + ret = QT_FSEEK(fh, QT_OFF_T(pos), SEEK_SET); + } while (ret == -1 && errno == EINTR); + + if (ret == -1) { + q->setError(QFile::ReadError, qt_error_string(int(errno))); + return false; + } + } else { + // Unbuffered stdio mode. + if (QT_LSEEK(fd, pos, SEEK_SET) == -1) { + qWarning("QFile::at: Cannot set file position %lld", 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); + + // Buffered stdlib mode. + if (fh) { + qint64 readBytes = 0; + qint64 read = 0; + int retry = 0; + + // Read in blocks of 4k to avoid platform limitations (Windows + // commonly bails out if you read or write too large blocks at once). + qint64 bytesToRead; + do { + if (retry == 1) + retry = 2; + + bytesToRead = qMin<qint64>(4096, len - read); + do { + readBytes = fread(data + read, 1, size_t(bytesToRead), fh); + } while (readBytes == 0 && !feof(fh) && errno == EINTR); + + if (readBytes > 0) { + read += readBytes; + } else if (!retry && feof(fh)) { + // Synchronize and try again (just once though). + if (++retry == 1) + QT_FSEEK(fh, QT_FTELL(fh), SEEK_SET); + } + } while (retry == 1 || (readBytes == bytesToRead && read < len)); + + // Return the number of bytes read, or if nothing was read, return -1 + // if an error occurred, or 0 if we detected EOF. + if (read == 0) { + q->setError(QFile::ReadError, qt_error_string(int(errno))); + if (!feof(fh)) + read = -1; + } + return read; + } + + // Unbuffered stdio mode. + qint64 ret = 0; + if (len) { + int result; + qint64 read = 0; + errno = 0; + + // Read in blocks of 4k to avoid platform limitations (Windows + // commonly bails out if you read or write too large blocks at once). + do { + qint64 bytesToRead = qMin<qint64>(4096, len - read); + do { + result = QT_READ(fd, data + read, int(bytesToRead)); + } while (result == -1 && errno == EINTR); + if (result > 0) + read += result; + } while (result > 0 && read < len); + + // Return the number of bytes read, or if nothing was read, return -1 + // if an error occurred. + if (read > 0) { + ret += read; + } else if (read == 0 && result < 0) { + ret = -1; + q->setError(QFile::ReadError, qt_error_string(errno)); + } + } + return ret; +} + +/*! + \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); + qint64 result; + qint64 written = 0; + + do { + // Write blocks of 4k to avoid platform limitations (Windows commonly + // bails out if you read or write too large blocks at once). + qint64 bytesToWrite = qMin<qint64>(4096, len - written); + if (fh) { + do { + // Buffered stdlib mode. + result = qint64(fwrite(data + written, 1, size_t(bytesToWrite), fh)); + } while (result == 0 && errno == EINTR); + if (bytesToWrite > 0 && result == 0) + result = -1; + } else { + do { + // Unbuffered stdio mode. + result = QT_WRITE(fd, data + written, bytesToWrite); + } while (result == -1 && errno == EINTR); + } + if (result > 0) + written += qint64(result); + } while (written < len && result > 0); + + // If we read anything, return that with success. Otherwise, set an error, + // and return the last return value. + if (result > 0) + return written; + q->setError(errno == ENOSPC ? QFile::ResourceError : QFile::WriteError, qt_error_string(errno)); + return result; +} + +/*! + \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 +*/ +bool QFSFileEnginePrivate::isSequentialFdFh() const +{ + if (!tried_stat) + doStat(); + if (could_stat) { +#ifdef Q_OS_UNIX + return (st.st_mode & S_IFMT) != S_IFREG; + // ### WINDOWS! +#endif + } + return true; +} + +/*! + \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; +} + +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 0000000..f54a6e8 --- /dev/null +++ b/src/corelib/io/qfsfileengine.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFSFILEENGINE_H +#define QFSFILEENGINE_H + +#include <QtCore/qabstractfileengine.h> + +#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; +#ifdef Q_OS_SYMBIAN + QString fileNameSymbian(FileName file) const; +#endif + 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); + 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 0000000..c5e23f2 --- /dev/null +++ b/src/corelib/io/qfsfileengine_iterator.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfsfileengine_iterator_p.h" +#include "qvariant.h" + +#ifndef QT_NO_FSFILEENGINE + +QT_BEGIN_NAMESPACE + +QFSFileEngineIterator::QFSFileEngineIterator(QDir::Filters filters, const QStringList &filterNames) + : QAbstractFileEngineIterator(filters, filterNames) +{ + newPlatformSpecifics(); +} + +QFSFileEngineIterator::~QFSFileEngineIterator() +{ + deletePlatformSpecifics(); +} + +QString QFSFileEngineIterator::next() +{ + if (!hasNext()) + return QString(); + + advance(); + return currentFilePath(); +} + +QString QFSFileEngineIterator::currentFileName() const +{ + return currentEntry; +} + +QFileInfo QFSFileEngineIterator::currentFileInfo() const +{ + return QAbstractFileEngineIterator::currentFileInfo(); +} + +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 0000000..7dcdbe2 --- /dev/null +++ b/src/corelib/io/qfsfileengine_iterator_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 "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: + QFSFileEngineIteratorPlatformSpecificData *platform; + friend class QFSFileEngineIteratorPlatformSpecificData; + void newPlatformSpecifics(); + void deletePlatformSpecifics(); + void advance(); + + QString currentEntry; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_FSFILEENGINE + +#endif diff --git a/src/corelib/io/qfsfileengine_iterator_unix.cpp b/src/corelib/io/qfsfileengine_iterator_unix.cpp new file mode 100644 index 0000000..fd95f81 --- /dev/null +++ b/src/corelib/io/qfsfileengine_iterator_unix.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qfsfileengine_iterator_p.h" + +#include <QtCore/qvariant.h> + +#ifndef QT_NO_FSFILEENGINE + +QT_BEGIN_NAMESPACE + +class QFSFileEngineIteratorPlatformSpecificData +{ +public: + inline QFSFileEngineIteratorPlatformSpecificData() + : dir(0), dirEntry(0), done(false) +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_CYGWIN) && !defined(Q_OS_SYMBIAN) + , mt_file(0) +#endif + { } + + DIR *dir; + dirent *dirEntry; + bool done; + +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_CYGWIN) && !defined(Q_OS_SYMBIAN) + // for readdir_r + dirent *mt_file; +#endif +}; + +void QFSFileEngineIterator::advance() +{ + currentEntry = platform->dirEntry ? QFile::decodeName(QByteArray(platform->dirEntry->d_name)) : QString(); + + if (!platform->dir) + return; + +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_CYGWIN) && !defined(Q_OS_SYMBIAN) + if (::readdir_r(platform->dir, platform->mt_file, &platform->dirEntry) != 0) + platform->done = true; +#else + // ### add local lock to prevent breaking reentrancy + platform->dirEntry = ::readdir(platform->dir); +#endif // _POSIX_THREAD_SAFE_FUNCTIONS + if (!platform->dirEntry) { + ::closedir(platform->dir); + platform->dir = 0; + platform->done = true; +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_CYGWIN) && !defined(Q_OS_SYMBIAN) + delete [] platform->mt_file; + platform->mt_file = 0; +#endif + } +} + +void QFSFileEngineIterator::newPlatformSpecifics() +{ + platform = new QFSFileEngineIteratorPlatformSpecificData; +} + +void QFSFileEngineIterator::deletePlatformSpecifics() +{ + if (platform->dir) { + ::closedir(platform->dir); +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_CYGWIN) && !defined(Q_OS_SYMBIAN) + delete [] platform->mt_file; + platform->mt_file = 0; +#endif + } + delete platform; + platform = 0; +} + +bool QFSFileEngineIterator::hasNext() const +{ + if (!platform->done && !platform->dir) { + QFSFileEngineIterator *that = const_cast<QFSFileEngineIterator *>(this); + if ((that->platform->dir = ::opendir(QFile::encodeName(path()).data())) == 0) { + that->platform->done = true; + } else { + // ### Race condition; we should use fpathconf and dirfd(). + long maxPathName = ::pathconf(QFile::encodeName(path()).data(), _PC_NAME_MAX); + if ((int) maxPathName == -1) + maxPathName = FILENAME_MAX; + maxPathName += sizeof(dirent) + 1; +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_CYGWIN) && !defined(Q_OS_SYMBIAN) + if (that->platform->mt_file) + delete [] that->platform->mt_file; + that->platform->mt_file = (dirent *)new char[maxPathName]; +#endif + + that->advance(); + } + } + return !platform->done; +} + +QT_END_NAMESPACE + +#endif // QT_NO_FSFILEENGINE diff --git a/src/corelib/io/qfsfileengine_iterator_win.cpp b/src/corelib/io/qfsfileengine_iterator_win.cpp new file mode 100644 index 0000000..3212d73 --- /dev/null +++ b/src/corelib/io/qfsfileengine_iterator_win.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdebug.h" +#include "qfsfileengine_iterator_p.h" +#include "qfsfileengine_p.h" +#include "qplatformdefs.h" + +#include <QtCore/qvariant.h> +#include <QtCore/qmutex.h> +#include <private/qmutexpool_p.h> + +QT_BEGIN_NAMESPACE + +class QFSFileEngineIteratorPlatformSpecificData +{ +public: + inline QFSFileEngineIteratorPlatformSpecificData() + : uncShareIndex(-1), findFileHandle(INVALID_HANDLE_VALUE), + done(false), uncFallback(false) + { } + + QFSFileEngineIterator *it; + + QStringList uncShares; + int uncShareIndex; + + HANDLE findFileHandle; + WIN32_FIND_DATA findData; + bool done; + bool uncFallback; + + void advance(); + void saveCurrentFileName(); +}; + +void QFSFileEngineIteratorPlatformSpecificData::saveCurrentFileName() +{ + if (uncFallback) { + // Windows share / UNC path + it->currentEntry = uncShares.at(uncShareIndex - 1); + } else { + // Local directory + QT_WA({ + it->currentEntry = QString::fromUtf16((unsigned short *)findData.cFileName); + } , { + it->currentEntry = QString::fromLocal8Bit((const char *)findData.cFileName); + }); + } +} + +void QFSFileEngineIterator::advance() +{ + platform->saveCurrentFileName(); + + if (platform->done) + return; + + if (platform->uncFallback) { + ++platform->uncShareIndex; + } else if (platform->findFileHandle != INVALID_HANDLE_VALUE) { + QT_WA({ + if (!FindNextFile(platform->findFileHandle, &platform->findData)) { + platform->done = true; + FindClose(platform->findFileHandle); + } + } , { + if (!FindNextFileA(platform->findFileHandle, (WIN32_FIND_DATAA *)&platform->findData)) { + platform->done = true; + FindClose(platform->findFileHandle); + } + }); + } +} + +void QFSFileEngineIterator::newPlatformSpecifics() +{ + platform = new QFSFileEngineIteratorPlatformSpecificData; + platform->it = this; +} + +void QFSFileEngineIterator::deletePlatformSpecifics() +{ + delete platform; + platform = 0; +} + +bool QFSFileEngineIterator::hasNext() const +{ + if (platform->done) + return false; + + if (platform->uncFallback) + return platform->uncShareIndex > 0 && platform->uncShareIndex <= platform->uncShares.size(); + + if (platform->findFileHandle == INVALID_HANDLE_VALUE) { + QString path = this->path(); + // Local directory + if (path.endsWith(QLatin1String(".lnk"))) + path = QFileInfo(path).readLink(); + + if (!path.endsWith(QLatin1Char('/'))) + path.append(QLatin1Char('/')); + path.append(QLatin1String("*.*")); + + QT_WA({ + QString fileName = QFSFileEnginePrivate::longFileName(path); + platform->findFileHandle = FindFirstFileW((TCHAR *)fileName.utf16(), + &platform->findData); + }, { + // Cast is safe, since char is at end of WIN32_FIND_DATA + platform->findFileHandle = FindFirstFileA(QFSFileEnginePrivate::win95Name(path), + (WIN32_FIND_DATAA*)&platform->findData); + }); + + if (platform->findFileHandle == INVALID_HANDLE_VALUE) { + if (path.startsWith(QLatin1String("//"))) { + path = this->path(); + // UNC + QStringList parts = QDir::toNativeSeparators(path).split(QLatin1Char('\\'), QString::SkipEmptyParts); + + if (parts.count() == 1 && QFSFileEnginePrivate::uncListSharesOnServer(QLatin1String("\\\\") + parts.at(0), + &platform->uncShares)) { + if (platform->uncShares.isEmpty()) { + platform->done = true; + } else { + platform->uncShareIndex = 1; + } + platform->uncFallback = true; + } else { + platform->done = true; + } + } else { + platform->done = true; + } + } + + if (!platform->done && (!platform->uncFallback || !platform->uncShares.isEmpty())) + platform->saveCurrentFileName(); + } + + return !platform->done; +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfsfileengine_p.h b/src/corelib/io/qfsfileengine_p.h new file mode 100644 index 0000000..8b6cc53 --- /dev/null +++ b/src/corelib/io/qfsfileengine_p.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 <qhash.h> + +#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 QFSFileEnginePrivate : public QAbstractFileEnginePrivate +{ + Q_DECLARE_PUBLIC(QFSFileEngine) + +public: +#ifdef Q_WS_WIN + static QByteArray win95Name(const QString &path); + static QString longFileName(const QString &path); +#endif + static QString canonicalized(const QString &path); + + QString filePath; + QByteArray nativeFilePath; + QIODevice::OpenMode openMode; + + void nativeInitFileName(); + 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; + qint64 sizeFdFh() const; + 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; + bool isSequentialFdFh() const; + + uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags); + bool unmap(uchar *ptr); + + FILE *fh; +#ifdef Q_WS_WIN + HANDLE fileHandle; + QHash<uchar *, QPair<int /*offset*/, HANDLE /*handle*/> > maps; + mutable int cachedFd; + mutable DWORD fileAttrib; +#else + QHash<uchar *, QPair<int /*offset*/, int /*handle|len*/> > maps; + mutable QT_STATBUF st; +#endif + int fd; + +#ifdef Q_USE_DEPRECATED_MAP_API + void mapHandleClose(); + HANDLE fileMapHandle; +#endif + + 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; +#ifdef Q_OS_UNIX + mutable uint need_lstat : 1; + mutable uint is_link : 1; +#endif + bool doStat() const; + bool isSymlink() const; + +#if defined(Q_OS_WIN32) + int sysOpen(const QString &, int flags); +#endif + +#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) + static void resolveLibs(); + static bool resolveUNCLibs_NT(); + static bool resolveUNCLibs_9x(); + static bool uncListSharesOnServer(const QString &server, QStringList *list); +#endif + +protected: + QFSFileEnginePrivate(); + + void init(); + +#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) + QAbstractFileEngine::FileFlags getPermissions() const; + QString getLink() const; +#endif +}; + +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 0000000..4747bcc --- /dev/null +++ b/src/corelib/io/qfsfileengine_unix.cpp @@ -0,0 +1,1293 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qabstractfileengine.h" +#include "private/qfsfileengine_p.h" + +#ifndef QT_NO_FSFILEENGINE + +#ifndef QT_NO_REGEXP +# include "qregexp.h" +#endif +#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 <syslimits.h> +# include <f32file.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 + + +#ifdef 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 + +/*! + \internal + + Returns the stdlib open string corresponding to a QIODevice::OpenMode. +*/ +static QByteArray openModeToFopenMode(QIODevice::OpenMode flags, const QString &fileName = QString()) +{ + QByteArray mode; + if ((flags & QIODevice::ReadOnly) && !(flags & QIODevice::Truncate)) { + mode = "rb"; + if (flags & QIODevice::WriteOnly) { + if (!fileName.isEmpty() &&QFile::exists(fileName)) + mode = "rb+"; + 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 += "+"; + } + return mode; +} + +/*! + \internal + + Returns the stdio open flags corresponding to a QIODevice::OpenMode. +*/ +static 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; + } + +#ifdef O_CLOEXEC + // supported on Linux >= 2.6.23; avoids one extra system call + // and avoids a race condition: if another thread forks, we could + // end up leaking a file descriptor... + oflags |= O_CLOEXEC; +#endif + return oflags; +} + +/*! + \internal + + Sets the file descriptor to close on exec. That is, the file + descriptor is not inherited by child processes. +*/ +static bool setCloseOnExec(int fd) +{ + return fd != -1 && fcntl(fd, F_SETFD, FD_CLOEXEC) != -1; +} + +/*! + \internal +*/ +void QFSFileEnginePrivate::nativeInitFileName() +{ + nativeFilePath = QFile::encodeName(filePath); +} + +/*! + \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(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; + } + + QT_STATBUF statBuf; + if (QT_FSTAT(fd, &statBuf) != -1) { + if ((statBuf.st_mode & S_IFMT) == S_IFDIR) { + q->setError(QFile::OpenError, QLatin1String("file to open is a directory")); + QT_CLOSE(fd); + return false; + } + } + +#ifndef O_CLOEXEC + // not needed on Linux >= 2.6.23 + setCloseOnExec(fd); // ignore failure +#endif + + // 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, filePath); + + // Try to open the file in buffered mode. + do { + fh = QT_FOPEN(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; + } + + QT_STATBUF statBuf; + if (QT_FSTAT(fileno(fh), &statBuf) != -1) { + if ((statBuf.st_mode & S_IFMT) == S_IFDIR) { + 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; +} + +/*! + \internal +*/ +bool QFSFileEnginePrivate::nativeClose() +{ + return closeFdFh(); +} + +/*! + \internal + +*/ +bool QFSFileEnginePrivate::nativeFlush() +{ + return fh ? flushFh() : fd != -1; +} + +/*! + \internal +*/ +qint64 QFSFileEnginePrivate::nativeRead(char *data, qint64 len) +{ + Q_Q(QFSFileEngine); + + 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 + int v = 1; + if ((oldFlags & O_NONBLOCK) == 0) + fcntl(QT_FILENO(fh), F_SETFL, oldFlags | O_NONBLOCK, &v, sizeof(v)); + + // 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) { + int v = 1; + fcntl(QT_FILENO(fh), F_SETFL, oldFlags, &v, sizeof(v)); + 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) { + int v = 1; + fcntl(QT_FILENO(fh), F_SETFL, oldFlags, &v, sizeof(v)); + } + 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) +{ + return writeFdFh(data, len); +} + +/*! + \internal +*/ +qint64 QFSFileEnginePrivate::nativePos() const +{ + return posFdFh(); +} + +/*! + \internal +*/ +bool QFSFileEnginePrivate::nativeSeek(qint64 pos) +{ + return seekFdFh(pos); +} + +/*! + \internal +*/ +int QFSFileEnginePrivate::nativeHandle() const +{ + return fh ? fileno(fh) : fd; +} + +/*! + \internal +*/ +bool QFSFileEnginePrivate::nativeIsSequential() const +{ + return isSequentialFdFh(); +} + +bool QFSFileEngine::remove() +{ + Q_D(QFSFileEngine); + return unlink(d->nativeFilePath.constData()) == 0; +} + +bool QFSFileEngine::copy(const QString &newName) +{ +#if defined(Q_OS_SYMBIAN) + Q_D(QFSFileEngine); + RFs rfs; + TInt err = rfs.Connect(); + if (err == KErrNone) { + CFileMan* fm = NULL; + HBufC* oldBuf = NULL; + HBufC* newBuf = NULL; + TRAP (err, + fm = CFileMan::NewL(rfs); + oldBuf = qt_QString2HBufCNewL(QDir::toNativeSeparators(d->filePath)); + RFile rfile; + err = rfile.Open(rfs, *oldBuf, EFileShareReadersOrWriters); + if (err == KErrNone) { + QFileInfo fi(newName); + QString absoluteNewName = fi.absolutePath() + QDir::separator() + fi.fileName(); + newBuf = qt_QString2HBufCNewL(QDir::toNativeSeparators(absoluteNewName)); + err = fm->Copy(rfile, *newBuf); + rfile.Close(); + } + ) // End TRAP + delete oldBuf; + delete newBuf; + delete fm; + rfs.Close(); + } + return (err == KErrNone); +#else + // ### Add copy code for Unix here + return false; +#endif +} + +bool QFSFileEngine::rename(const QString &newName) +{ + Q_D(QFSFileEngine); + return ::rename(d->nativeFilePath.constData(), QFile::encodeName(newName).constData()) == 0; +} + +bool QFSFileEngine::link(const QString &newName) +{ + Q_D(QFSFileEngine); + return ::symlink(d->nativeFilePath.constData(), QFile::encodeName(newName).constData()) == 0; +} + +qint64 QFSFileEnginePrivate::nativeSize() const +{ + return sizeFdFh(); +} + +bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const +{ + QString dirName = name; + if (createParentDirectories) { +#if defined(Q_OS_SYMBIAN) + dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); +#else + dirName = QDir::cleanPath(dirName); +#endif + 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 (::mkdir(chunk, 0777) != 0) { + return false; + } + } + } + return true; + } +#if defined(Q_OS_DARWIN) // Mac X doesn't support trailing /'s + if (dirName[dirName.length() - 1] == QLatin1Char('/')) + dirName = dirName.left(dirName.length() - 1); +#endif + return (::mkdir(QFile::encodeName(dirName), 0777) == 0); +} + +bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const +{ + QString dirName = name; + if (recurseParentDirectories) { +#if defined(Q_OS_SYMBIAN) + dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); +#else + dirName = QDir::cleanPath(dirName); +#endif + 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(dirName)) == 0; +} + +bool QFSFileEngine::caseSensitive() const +{ +#if defined(Q_OS_SYMBIAN) + return false; +#else + return true; +#endif +} + +bool QFSFileEngine::setCurrentPath(const QString &path) +{ + int r; + r = ::chdir(QFile::encodeName(path)); + return r >= 0; +} + +QString QFSFileEngine::currentPath(const QString &) +{ + QString result; + QT_STATBUF st; +#if defined(Q_OS_SYMBIAN) + char currentName[PATH_MAX+1]; + if (::getcwd(currentName, PATH_MAX)) + result = QDir::fromNativeSeparators(QFile::decodeName(QByteArray(currentName))); + if (result.isEmpty()) { +# if defined(QT_DEBUG) + qWarning("QDir::currentPath: getcwd() failed"); +# endif + } else +#endif + 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); + } +#elif !defined(Q_OS_SYMBIAN) + char currentName[PATH_MAX+1]; + if (::getcwd(currentName, PATH_MAX)) + result = QFile::decodeName(QByteArray(currentName)); +# if defined(QT_DEBUG) + if (result.isNull()) + qWarning("QDir::currentPath: getcwd() failed"); +# endif +#endif + } else { +#if defined(Q_OS_SYMBIAN) + // If current dir returned by Open C doesn't exist, + // try to create it (can happen with application private dirs) + // Ignore mkdir failures; we want to be consistent with Open C + // current path regardless. + ::mkdir(QFile::encodeName(currentName), 0777); +#else +# if defined(QT_DEBUG) + qWarning("QDir::currentPath: stat(\".\") failed"); +# endif +#endif + } + return result; +} + +QString QFSFileEngine::homePath() +{ + QString home = QFile::decodeName(qgetenv("HOME")); +#if defined(Q_OS_SYMBIAN) + if (home.isEmpty()) + home = QLatin1String("C:/Data"); +#else + if (home.isNull()) + home = rootPath(); +#endif + return home; +} + +QString QFSFileEngine::rootPath() +{ +#if defined(Q_OS_SYMBIAN) + return QString::fromLatin1("C:/"); +#else + return QString::fromLatin1("/"); +#endif +} + +QString QFSFileEngine::tempPath() +{ +#ifdef Q_OS_SYMBIAN + QString temp = QDir::currentPath().left(2); + temp += QString::fromLatin1( "/system/temp/"); +#else + QString temp = QFile::decodeName(qgetenv("TMPDIR")); + if (temp.isEmpty()) + temp = QString::fromLatin1("/tmp/"); +#endif + return temp; +} + +QFileInfoList QFSFileEngine::drives() +{ + QFileInfoList ret; +#if defined(Q_OS_SYMBIAN) + TDriveList driveList; + RFs rfs; + TInt err = rfs.Connect(); + if (err == KErrNone) { + err = rfs.DriveList(driveList); + if (err == KErrNone) { + for(char i=0; i < KMaxDrives; i++) { + if (driveList[i]) { + ret.append(QString("%1:/").arg(QChar('A'+i))); + } + } + } else { + qWarning("QDir::drives: Getting drives failed"); + } + rfs.Close(); + } +#else + ret.append(rootPath()); +#endif + return ret; +} + +bool QFSFileEnginePrivate::doStat() const +{ + if (tried_stat == 0) { + QFSFileEnginePrivate *that = const_cast<QFSFileEnginePrivate*>(this); + if (fh && nativeFilePath.isEmpty()) { + // ### actually covers two cases: d->fh and when the file is not open + that->could_stat = (QT_FSTAT(fileno(fh), &st) == 0); + } else if (fd == -1) { + // ### actually covers two cases: d->fh and when the file is not open + that->could_stat = (QT_STAT(nativeFilePath.constData(), &st) == 0); + } else { + that->could_stat = (QT_FSTAT(fd, &st) == 0); + } + that->tried_stat = 1; + } + return could_stat; +} + +bool QFSFileEnginePrivate::isSymlink() const +{ + if (need_lstat) { + QFSFileEnginePrivate *that = const_cast<QFSFileEnginePrivate *>(this); + that->need_lstat = false; + QT_STATBUF st; // don't clobber our main one + that->is_link = (QT_LSTAT(nativeFilePath.constData(), &st) == 0) ? S_ISLNK(st.st_mode) : false; + } + return is_link; +} + +#if defined(Q_OS_SYMBIAN) +static bool _q_isSymbianHidden(const QString &path, bool isDir) +{ + bool retval = false; + RFs rfs; + TInt err = rfs.Connect(); + if (err == KErrNone) { + QFileInfo fi(path); + QString absPath = fi.absoluteFilePath(); + if (isDir && absPath.at(absPath.size()-1) != QChar('/')) { + absPath += QChar('/'); + } + HBufC* buffer = qt_QString2HBufCNewL(QDir::toNativeSeparators(absPath)); + TUint attributes; + err = rfs.Att(*buffer, attributes); + delete buffer; + rfs.Close(); + if (err == KErrNone && (attributes & KEntryAttHidden)) { + retval = true; + } + } + + return retval; +} +#endif + +#if !defined(QWS) && defined(Q_OS_MAC) +static bool _q_isMacHidden(const QString &path) +{ + OSErr err = noErr; + + FSRef fsRef; + +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { + err = FSPathMakeRefWithOptions(reinterpret_cast<const UInt8 *>(QFile::encodeName(QDir::cleanPath(path)).constData()), + kFSPathMakeRefDoNotFollowLeafSymlink, &fsRef, 0); + } else +#endif + { + QFileInfo fi(path); + FSRef parentRef; + err = FSPathMakeRef(reinterpret_cast<const UInt8 *>(fi.absoluteDir().absolutePath().toUtf8().constData()), + &parentRef, 0); + if (err == noErr) { + QString fileName = fi.fileName(); + err = FSMakeFSRefUnicode(&parentRef, fileName.length(), + reinterpret_cast<const UniChar *>(fileName.unicode()), + kTextEncodingUnknown, &fsRef); + } + } + 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); + bool result = (fileInfo->finderFlags & kIsInvisible); + return result; +} +#endif + +/*! + \reimp +*/ +QAbstractFileEngine::FileFlags QFSFileEngine::fileFlags(FileFlags type) const +{ + Q_D(const QFSFileEngine); + // Force a stat, so that we're guaranteed to get up-to-date results + if (type & QAbstractFileEngine::FileFlag(QAbstractFileEngine::Refresh)) { + d->tried_stat = 0; + d->need_lstat = 1; + } + + QAbstractFileEngine::FileFlags ret = 0; + bool exists = d->doStat(); + if (!exists && !d->isSymlink()) + return ret; + + if (exists && (type & PermsMask)) { + if (d->st.st_mode & S_IRUSR) + ret |= ReadOwnerPerm; + if (d->st.st_mode & S_IWUSR) + ret |= WriteOwnerPerm; + if (d->st.st_mode & S_IXUSR) + ret |= ExeOwnerPerm; + if (d->st.st_mode & S_IRUSR) + ret |= ReadUserPerm; + if (d->st.st_mode & S_IWUSR) + ret |= WriteUserPerm; + if (d->st.st_mode & S_IXUSR) + ret |= ExeUserPerm; + if (d->st.st_mode & S_IRGRP) + ret |= ReadGroupPerm; + if (d->st.st_mode & S_IWGRP) + ret |= WriteGroupPerm; + if (d->st.st_mode & S_IXGRP) + ret |= ExeGroupPerm; + if (d->st.st_mode & S_IROTH) + ret |= ReadOtherPerm; + if (d->st.st_mode & S_IWOTH) + ret |= WriteOtherPerm; + if (d->st.st_mode & S_IXOTH) + ret |= ExeOtherPerm; + } + if (type & TypesMask) { +#if !defined(QWS) && defined(Q_OS_MAC) + bool foundAlias = false; + { + FSRef fref; + if (FSPathMakeRef((const UInt8 *)QFile::encodeName(QDir::cleanPath(d->filePath)).data(), + &fref, NULL) == noErr) { + Boolean isAlias, isFolder; + if (FSIsAliasFile(&fref, &isAlias, &isFolder) == noErr && isAlias) { + foundAlias = true; + ret |= LinkType; + } + } + } + if (!foundAlias) +#endif + { + if ((type & LinkType) && d->isSymlink()) + ret |= LinkType; + if (exists && (d->st.st_mode & S_IFMT) == S_IFREG) + ret |= FileType; + else if (exists && (d->st.st_mode & S_IFMT) == S_IFDIR) + ret |= DirectoryType; +#if !defined(QWS) && defined(Q_OS_MAC) + if((ret & DirectoryType) && (type & BundleType)) { + QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, QCFString(d->filePath), + kCFURLPOSIXPathStyle, true); + UInt32 type, creator; + if(CFBundleGetPackageInfoInDirectory(url, &type, &creator)) + ret |= BundleType; + } +#endif + } + } + if (type & FlagsMask) { + ret |= LocalDiskFlag; + if (exists) + ret |= ExistsFlag; +#if defined(Q_OS_SYMBIAN) + if (d->filePath == QLatin1String("/") || (d->filePath.at(0).isLetter() && d->filePath.mid(1,d->filePath.length()) == QLatin1String(":/"))) + ret |= RootFlag; + + // In Symbian, all symlinks have hidden attribute for some reason; + // lets make them visible for better compatibility with other platforms. + // If somebody actually wants a hidden link, then they are out of luck. + if (!(ret & RootFlag) && !d->isSymlink()) + if(_q_isSymbianHidden(d->filePath, ret & DirectoryType) +#else + if (fileName(BaseName)[0] == QLatin1Char('.') +# if !defined(QWS) && defined(Q_OS_MAC) + || _q_isMacHidden(d->filePath) +# endif +#endif + ) + ret |= HiddenFlag; +#if !defined(Q_OS_SYMBIAN) + if (d->filePath == QLatin1String("/")) + ret |= RootFlag; +#endif + } + return ret; +} + +#ifdef Q_OS_SYMBIAN +QString QFSFileEngine::fileNameSymbian(FileName file) const +{ + Q_D(const QFSFileEngine); + if(file == BaseName) { + int slash = d->filePath.lastIndexOf(QLatin1Char('/')); + if(slash == -1) { + int colon = d->filePath.lastIndexOf(QLatin1Char(':')); + if(colon != -1) + return d->filePath.mid(colon + 1); + return d->filePath; + } + return d->filePath.mid(slash + 1); + } else if(file == PathName) { + if(!d->filePath.size()) + return d->filePath; + + int slash = d->filePath.lastIndexOf(QLatin1Char('/')); + if(slash == -1) { + if(d->filePath.length() >= 2 && d->filePath.at(1) == QLatin1Char(':')) + return d->filePath.left(2); + return QString::fromLatin1("."); + } else { + if(!slash) + return QString::fromLatin1("/"); + if(slash == 2 && d->filePath.length() >= 2 && d->filePath.at(1) == QLatin1Char(':')) + slash++; + return d->filePath.left(slash); + } + } else if(file == AbsoluteName || file == AbsolutePathName) { + QString ret; + if (!isRelativePath()) { + if (d->filePath.size() > 2 && d->filePath.at(1) == QLatin1Char(':') + && d->filePath.at(2) != QLatin1Char('/') || // It's a drive-relative path, so Z:a.txt -> Z:\currentpath\a.txt + d->filePath.startsWith(QLatin1Char('/')) // It's a absolute path to the current drive, so \a.txt -> Z:\a.txt + ) { + ret = QString(QDir::currentPath().left(2) + QDir::fromNativeSeparators(d->filePath)); + } else { + ret = d->filePath; + } + } else { + ret = QDir::cleanPath(QDir::currentPath() + QLatin1Char('/') + d->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(); + + QString ret = QFSFileEnginePrivate::canonicalized(fileName(AbsoluteName)); + if (!ret.isEmpty() && file == CanonicalPathName) { + int slash = ret.lastIndexOf(QLatin1Char('/')); + if (slash == -1) + ret = QDir::fromNativeSeparators(QDir::currentPath()); + else if (slash == 0) + ret = QLatin1String("/"); + ret = ret.left(slash); + } + return ret; + } else if(file == LinkName) { + if (d->isSymlink()) { + char s[PATH_MAX+1]; + int len = readlink(d->nativeFilePath.constData(), s, PATH_MAX); + if (len > 0) { + s[len] = '\0'; + QString ret = QFile::decodeName(QByteArray(s)); + + if (isRelativePathSymbian(ret)) { + if (!isRelativePathSymbian(d->filePath)) { + ret.prepend(d->filePath.left(d->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 ret; + } + } + return QString(); + } else if(file == BundleName) { + return QString(); + } + return d->filePath; +} +#endif + +QString QFSFileEngine::fileName(FileName file) const +{ +#ifdef Q_OS_SYMBIAN + return fileNameSymbian(file); +#endif + Q_D(const QFSFileEngine); + if (file == BundleName) { +#if !defined(QWS) && defined(Q_OS_MAC) + QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, QCFString(d->filePath), + kCFURLPOSIXPathStyle, true); + if(CFDictionaryRef dict = CFBundleCopyInfoDictionaryForURL(url)) { + if(CFTypeRef name = (CFTypeRef)CFDictionaryGetValue(dict, kCFBundleNameKey)) { + if(CFGetTypeID(name) == CFStringGetTypeID()) + return QCFString::toQString((CFStringRef)name); + } + } +#endif + return QString(); + } else if (file == BaseName) { + int slash = d->filePath.lastIndexOf(QLatin1Char('/')); + if (slash != -1) + return d->filePath.mid(slash + 1); + } else if (file == PathName) { + int slash = d->filePath.lastIndexOf(QLatin1Char('/')); + if (slash == -1) + return QLatin1String("."); + else if (!slash) + return QLatin1String("/"); + return d->filePath.left(slash); + } else if (file == AbsoluteName || file == AbsolutePathName) { + QString ret; + if (d->filePath.isEmpty() || !d->filePath.startsWith(QLatin1Char('/'))) + ret = QDir::currentPath(); + if (!d->filePath.isEmpty() && d->filePath != QLatin1String(".")) { + if (!ret.isEmpty() && !ret.endsWith(QLatin1Char('/'))) + ret += QLatin1Char('/'); + ret += d->filePath; + } + if (ret == QLatin1String("/")) + return ret; + bool isDir = ret.endsWith(QLatin1Char('/')); + ret = QDir::cleanPath(ret); + if (isDir) + ret += QLatin1String("/"); + if (file == AbsolutePathName) { + int slash = ret.lastIndexOf(QLatin1Char('/')); + if (slash == -1) + return QDir::currentPath(); + else if (!slash) + return QLatin1String("/"); + return ret.left(slash); + } + return ret; + } else if (file == CanonicalName || file == CanonicalPathName) { + if (!(fileFlags(ExistsFlag) & ExistsFlag)) + return QString(); + + QString ret = QFSFileEnginePrivate::canonicalized(fileName(AbsoluteName)); + if (!ret.isEmpty() && file == CanonicalPathName) { + int slash = ret.lastIndexOf(QLatin1Char('/')); + if (slash == -1) + ret = QDir::currentPath(); + else if (slash == 0) + ret = QLatin1String("/"); + ret = ret.left(slash); + } + return ret; + } else if (file == LinkName) { + if (d->isSymlink()) { +#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); + if (s == 0) { + len = -1; + break; + } + len = ::readlink(d->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(d->nativeFilePath.constData(), s, PATH_MAX); +#endif + if (len > 0) { + QString ret; + if (S_ISDIR(d->st.st_mode) && s[0] != '/') { + QDir parent(d->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 (d->filePath.startsWith(QLatin1Char('/'))) { + ret.prepend(d->filePath.left(d->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 ret; + } + } +#if !defined(QWS) && defined(Q_OS_MAC) + { + FSRef fref; + if (FSPathMakeRef((const UInt8 *)QFile::encodeName(QDir::cleanPath(d->filePath)).data(), &fref, 0) == noErr) { + Boolean isAlias, isFolder; + if (FSResolveAliasFile(&fref, true, &isFolder, &isAlias) == noErr && isAlias) { + AliasHandle alias; + if (FSNewAlias(0, &fref, &alias) == noErr && alias) { + CFStringRef cfstr; + if (FSCopyAliasInfo(alias, 0, 0, &cfstr, 0, 0) == noErr) + return QCFString::toQString(cfstr); + } + } + } + } +#endif + return QString(); + } + return d->filePath; +} + +bool QFSFileEngine::isRelativePath() const +{ + Q_D(const QFSFileEngine); +#ifdef Q_OS_SYMBIAN + return isRelativePathSymbian(d->filePath); +#else + int len = d->filePath.length(); + if (len == 0) + return true; + return d->filePath[0] != QLatin1Char('/'); +#endif +} + +uint QFSFileEngine::ownerId(FileOwner own) const +{ + Q_D(const QFSFileEngine); + static const uint nobodyID = (uint) -2; + if (d->doStat()) { + if (own == OwnerUser) + return d->st.st_uid; + else + return d->st.st_gid; + } + return nobodyID; +} + +QString QFSFileEngine::owner(FileOwner own) const +{ +#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 + + if (own == OwnerUser) { + struct passwd *pw = 0; +#if !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) + struct passwd entry; + getpwuid_r(ownerId(own), &entry, buf.data(), buf.size(), &pw); +#else + pw = getpwuid(ownerId(own)); +#endif + if (pw) + return QFile::decodeName(QByteArray(pw->pw_name)); + } else if (own == OwnerGroup) { +#ifdef Q_OS_SYMBIAN + return QString(); +#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(ownerId(own), &entry, buf.data(), buf.size(), &gr) + || errno != ERANGE) + break; + } + +#else + gr = getgrgid(ownerId(own)); +#endif + if (gr) + return QFile::decodeName(QByteArray(gr->gr_name)); + } + return QString(); +} + +bool QFSFileEngine::setPermissions(uint perms) +{ + Q_D(QFSFileEngine); + mode_t mode = 0; + if (perms & ReadOwnerPerm) + mode |= S_IRUSR; + if (perms & WriteOwnerPerm) + mode |= S_IWUSR; + if (perms & ExeOwnerPerm) + mode |= S_IXUSR; + if (perms & ReadUserPerm) + mode |= S_IRUSR; + if (perms & WriteUserPerm) + mode |= S_IWUSR; + if (perms & ExeUserPerm) + mode |= S_IXUSR; + if (perms & ReadGroupPerm) + mode |= S_IRGRP; + if (perms & WriteGroupPerm) + mode |= S_IWGRP; + if (perms & ExeGroupPerm) + mode |= S_IXGRP; + if (perms & ReadOtherPerm) + mode |= S_IROTH; + if (perms & WriteOtherPerm) + mode |= S_IWOTH; + if (perms & ExeOtherPerm) + mode |= S_IXOTH; + if (d->fd != -1) + return !fchmod(d->fd, mode); + return !::chmod(d->nativeFilePath.constData(), mode); +} + +bool QFSFileEngine::setSize(qint64 size) +{ + Q_D(QFSFileEngine); + if (d->fd != -1) + return !QT_FTRUNCATE(d->fd, size); + return !QT_TRUNCATE(d->nativeFilePath.constData(), size); +} + +QDateTime QFSFileEngine::fileTime(FileTime time) const +{ + Q_D(const QFSFileEngine); + QDateTime ret; + if (d->doStat()) { + if (time == CreationTime) + ret.setTime_t(d->st.st_ctime ? d->st.st_ctime : d->st.st_mtime); + else if (time == ModificationTime) + ret.setTime_t(d->st.st_mtime); + else if (time == AccessTime) + ret.setTime_t(d->st.st_atime); + } + return ret; +} + +uchar *QFSFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags) +{ + Q_Q(QFSFileEngine); + Q_UNUSED(flags); + if (offset < 0) { + q->setError(QFile::UnspecifiedError, qt_error_string(int(EINVAL))); + return 0; + } + if (openMode == QIODevice::NotOpen) { + q->setError(QFile::PermissionsError, qt_error_string(int(EACCES))); + return 0; + } + int access = 0; + if (openMode & QIODevice::ReadOnly) access |= PROT_READ; + if (openMode & QIODevice::WriteOnly) access |= PROT_WRITE; + + int pagesSize = getpagesize(); + int realOffset = offset / pagesSize; + int extra = offset % pagesSize; + + void *mapAddress = mmap((void*)0, (size_t)size + extra, + access, MAP_SHARED, nativeHandle(), realOffset * pagesSize); + if (MAP_FAILED != mapAddress) { + uchar *address = extra + static_cast<uchar*>(mapAddress); + maps[address] = QPair<int,int>(extra, size); + 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) +{ + Q_Q(QFSFileEngine); + if (!maps.contains(ptr)) { + q->setError(QFile::PermissionsError, qt_error_string(EACCES)); + return false; + } + + uchar *start = ptr - maps[ptr].first; + int len = maps[ptr].second; + if (-1 == munmap(start, len)) { + q->setError(QFile::UnspecifiedError, qt_error_string(errno)); + return false; + } + maps.remove(ptr); + return true; +} + +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 0000000..a57c00e --- /dev/null +++ b/src/corelib/io/qfsfileengine_win.cpp @@ -0,0 +1,2242 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#define _POSIX_ +#include "qplatformdefs.h" +#include "qabstractfileengine.h" +#include "private/qfsfileengine_p.h" +#include <qdebug.h> + +#include "qfile.h" +#include "qdir.h" +#include "qtemporaryfile.h" +#ifndef QT_NO_REGEXP +# include "qregexp.h" +#endif +#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> +#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 +#ifdef Q_CC_MINGW +// A workaround for a certain version of MinGW, the define UNICODE_STRING. +#include <subauth.h> +#endif +#include <security.h> + +#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 + +QT_BEGIN_NAMESPACE + +static QString readLink(const QString &link); + +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 DECLSPEC_IMPORT BOOL (WINAPI *PtrLookupAccountSidW)(LPCWSTR, PSID, LPWSTR, LPDWORD, LPWSTR, LPDWORD, PSID_NAME_USE); +static PtrLookupAccountSidW ptrLookupAccountSidW = 0; +typedef DECLSPEC_IMPORT BOOL (WINAPI *PtrAllocateAndInitializeSid)(PSID_IDENTIFIER_AUTHORITY, BYTE, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, PSID*); +static PtrAllocateAndInitializeSid ptrAllocateAndInitializeSid = 0; +typedef VOID (WINAPI *PtrBuildTrusteeWithSidW)(PTRUSTEE_W, PSID); +static PtrBuildTrusteeWithSidW ptrBuildTrusteeWithSidW = 0; +typedef VOID (WINAPI *PtrBuildTrusteeWithNameW)(PTRUSTEE_W, unsigned short*); +static PtrBuildTrusteeWithNameW ptrBuildTrusteeWithNameW = 0; +typedef DWORD (WINAPI *PtrGetEffectiveRightsFromAclW)(PACL, PTRUSTEE_W, OUT PACCESS_MASK); +static PtrGetEffectiveRightsFromAclW ptrGetEffectiveRightsFromAclW = 0; +typedef DECLSPEC_IMPORT PVOID (WINAPI *PtrFreeSid)(PSID); +static PtrFreeSid ptrFreeSid = 0; +static TRUSTEE_W currentUserTrusteeW; + +typedef BOOL (WINAPI *PtrOpenProcessToken)(HANDLE, DWORD, PHANDLE ); +static PtrOpenProcessToken ptrOpenProcessToken = 0; +typedef BOOL (WINAPI *PtrGetUserProfileDirectoryW)( HANDLE, LPWSTR, LPDWORD); +static PtrGetUserProfileDirectoryW ptrGetUserProfileDirectoryW = 0; +typedef BOOL (WINAPI *PtrSetFilePointerEx)(HANDLE, LARGE_INTEGER, PLARGE_INTEGER, DWORD); +static PtrSetFilePointerEx ptrSetFilePointerEx = 0; +QT_END_INCLUDE_NAMESPACE + + +void QFSFileEnginePrivate::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) + if(QSysInfo::WindowsVersion & QSysInfo::WV_NT_based) { + HINSTANCE advapiHnd = LoadLibraryW(L"advapi32"); + if (advapiHnd) { + ptrGetNamedSecurityInfoW = (PtrGetNamedSecurityInfoW)GetProcAddress(advapiHnd, "GetNamedSecurityInfoW"); + ptrLookupAccountSidW = (PtrLookupAccountSidW)GetProcAddress(advapiHnd, "LookupAccountSidW"); + ptrAllocateAndInitializeSid = (PtrAllocateAndInitializeSid)GetProcAddress(advapiHnd, "AllocateAndInitializeSid"); + ptrBuildTrusteeWithSidW = (PtrBuildTrusteeWithSidW)GetProcAddress(advapiHnd, "BuildTrusteeWithSidW"); + ptrBuildTrusteeWithNameW = (PtrBuildTrusteeWithNameW)GetProcAddress(advapiHnd, "BuildTrusteeWithNameW"); + ptrGetEffectiveRightsFromAclW = (PtrGetEffectiveRightsFromAclW)GetProcAddress(advapiHnd, "GetEffectiveRightsFromAclW"); + ptrFreeSid = (PtrFreeSid)GetProcAddress(advapiHnd, "FreeSid"); + } + if (ptrBuildTrusteeWithNameW) { + HINSTANCE versionHnd = LoadLibraryW(L"version"); + if (versionHnd) { + typedef DWORD (WINAPI *PtrGetFileVersionInfoSizeW)(LPWSTR lptstrFilename,LPDWORD lpdwHandle); + PtrGetFileVersionInfoSizeW ptrGetFileVersionInfoSizeW = (PtrGetFileVersionInfoSizeW)GetProcAddress(versionHnd, "GetFileVersionInfoSizeW"); + typedef BOOL (WINAPI *PtrGetFileVersionInfoW)(LPWSTR lptstrFilename,DWORD dwHandle,DWORD dwLen,LPVOID lpData); + PtrGetFileVersionInfoW ptrGetFileVersionInfoW = (PtrGetFileVersionInfoW)GetProcAddress(versionHnd, "GetFileVersionInfoW"); + typedef BOOL (WINAPI *PtrVerQueryValueW)(const LPVOID pBlock,LPWSTR lpSubBlock,LPVOID *lplpBuffer,PUINT puLen); + PtrVerQueryValueW ptrVerQueryValueW = (PtrVerQueryValueW)GetProcAddress(versionHnd, "VerQueryValueW"); + if(ptrGetFileVersionInfoSizeW && ptrGetFileVersionInfoW && ptrVerQueryValueW) { + DWORD fakeHandle; + DWORD versionSize = ptrGetFileVersionInfoSizeW(L"secur32.dll", &fakeHandle); + if(versionSize) { + LPVOID versionData; + versionData = malloc(versionSize); + if(ptrGetFileVersionInfoW(L"secur32.dll", 0, versionSize, versionData)) { + UINT puLen; + VS_FIXEDFILEINFO *pLocalInfo; + if(ptrVerQueryValueW(versionData, L"\\", (void**)&pLocalInfo, &puLen)) { + WORD wVer1, wVer2, wVer3, wVer4; + wVer1 = HIWORD(pLocalInfo->dwFileVersionMS); + wVer2 = LOWORD(pLocalInfo->dwFileVersionMS); + wVer3 = HIWORD(pLocalInfo->dwFileVersionLS); + wVer4 = LOWORD(pLocalInfo->dwFileVersionLS); + // It will not work with secur32.dll version 5.0.2195.2862 + if(!(wVer1 == 5 && wVer2 == 0 && wVer3 == 2195 && (wVer4 == 2862 || wVer4 == 4587))) { + HINSTANCE userHnd = LoadLibraryW(L"secur32"); + if (userHnd) { + typedef BOOL (WINAPI *PtrGetUserNameExW)(EXTENDED_NAME_FORMAT nameFormat, ushort* lpBuffer, LPDWORD nSize); + PtrGetUserNameExW ptrGetUserNameExW = (PtrGetUserNameExW)GetProcAddress(userHnd, "GetUserNameExW"); + if(ptrGetUserNameExW) { + static TCHAR buffer[258]; + DWORD bufferSize = 257; + ptrGetUserNameExW(NameSamCompatible, (ushort*)buffer, &bufferSize); + ptrBuildTrusteeWithNameW(¤tUserTrusteeW, (ushort*)buffer); + } + FreeLibrary(userHnd); + } + } + } + } + free(versionData); + } + } + FreeLibrary(versionHnd); + } + } + ptrOpenProcessToken = (PtrOpenProcessToken)GetProcAddress(advapiHnd, "OpenProcessToken"); + HINSTANCE userenvHnd = LoadLibraryW(L"userenv"); + if (userenvHnd) { + ptrGetUserProfileDirectoryW = (PtrGetUserProfileDirectoryW)GetProcAddress(userenvHnd, "GetUserProfileDirectoryW"); + } + HINSTANCE kernelHnd = LoadLibraryW(L"kernel32"); + if (kernelHnd) + ptrSetFilePointerEx = (PtrSetFilePointerEx)GetProcAddress(kernelHnd, "SetFilePointerEx"); + } +#endif + } +} +#endif // QT_NO_LIBRARY + +// UNC functions NT +typedef DWORD (WINAPI *PtrNetShareEnum_NT)(LPWSTR, DWORD, LPBYTE*, DWORD, LPDWORD, LPDWORD, LPDWORD); +static PtrNetShareEnum_NT ptrNetShareEnum_NT = 0; +typedef DWORD (WINAPI *PtrNetApiBufferFree_NT)(LPVOID); +static PtrNetApiBufferFree_NT ptrNetApiBufferFree_NT = 0; +typedef struct _SHARE_INFO_1_NT { + LPWSTR shi1_netname; + DWORD shi1_type; + LPWSTR shi1_remark; +} SHARE_INFO_1_NT; + + +bool QFSFileEnginePrivate::resolveUNCLibs_NT() +{ + static bool triedResolve = false; + if (!triedResolve) { +#ifndef QT_NO_THREAD + QMutexLocker locker(QMutexPool::globalInstanceGet(&triedResolve)); + if (triedResolve) { + return ptrNetShareEnum_NT && ptrNetApiBufferFree_NT; + } +#endif + triedResolve = true; +#if !defined(Q_OS_WINCE) + HINSTANCE hLib = LoadLibraryW(L"Netapi32"); + if (hLib) { + ptrNetShareEnum_NT = (PtrNetShareEnum_NT)GetProcAddress(hLib, "NetShareEnum"); + if (ptrNetShareEnum_NT) + ptrNetApiBufferFree_NT = (PtrNetApiBufferFree_NT)GetProcAddress(hLib, "NetApiBufferFree"); + } +#endif + } + return ptrNetShareEnum_NT && ptrNetApiBufferFree_NT; +} + +// UNC functions 9x +typedef DWORD (WINAPI *PtrNetShareEnum_9x)(const char FAR *, short, char FAR *, unsigned short, unsigned short FAR *, unsigned short FAR *); +static PtrNetShareEnum_9x ptrNetShareEnum_9x = 0; +#ifdef LM20_NNLEN +# define LM20_NNLEN_9x LM20_NNLEN +#else +# define LM20_NNLEN_9x 12 +#endif +typedef struct _SHARE_INFO_1_9x { + char shi1_netname[LM20_NNLEN_9x+1]; + char shi1_pad1; + unsigned short shi1_type; + char FAR* shi1_remark; +} SHARE_INFO_1_9x; + +bool QFSFileEnginePrivate::resolveUNCLibs_9x() +{ + static bool triedResolve = false; + if (!triedResolve) { +#ifndef QT_NO_THREAD + QMutexLocker locker(QMutexPool::globalInstanceGet(&triedResolve)); + if (triedResolve) { + return ptrNetShareEnum_9x; + } +#endif + triedResolve = true; +#if !defined(Q_OS_WINCE) + HINSTANCE hLib = LoadLibraryA("Svrapi"); + if (hLib) + ptrNetShareEnum_9x = (PtrNetShareEnum_9x)GetProcAddress(hLib, "NetShareEnum"); +#endif + } + return ptrNetShareEnum_9x; +} + +bool QFSFileEnginePrivate::uncListSharesOnServer(const QString &server, QStringList *list) +{ + if (resolveUNCLibs_NT()) { + SHARE_INFO_1_NT *BufPtr, *p; + DWORD res; + DWORD er=0,tr=0,resume=0, i; + do { + res = ptrNetShareEnum_NT((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::fromUtf16((unsigned short *)p->shi1_netname)); + p++; + } + } + ptrNetApiBufferFree_NT(BufPtr); + } while (res==ERROR_MORE_DATA); + return res == ERROR_SUCCESS; + + } else if (resolveUNCLibs_9x()) { + SHARE_INFO_1_9x *pBuf = 0; + short cbBuffer; + unsigned short nEntriesRead = 0; + unsigned short nTotalEntries = 0; + short numBuffs = 20; + DWORD nStatus = 0; + do { + cbBuffer = numBuffs * sizeof(SHARE_INFO_1_9x); + pBuf = (SHARE_INFO_1_9x *)malloc(cbBuffer); + if (pBuf) { + nStatus = ptrNetShareEnum_9x(server.toLocal8Bit().constData(), 1, (char FAR *)pBuf, cbBuffer, &nEntriesRead, &nTotalEntries); + if ((nStatus == ERROR_SUCCESS)) { + for (int i = 0; i < nEntriesRead; ++i) { + if (list && pBuf[i].shi1_type == 0) + list->append(QString::fromLocal8Bit(pBuf[i].shi1_netname)); + } + free(pBuf); + break; + } + free(pBuf); + numBuffs *=2; + } + } while (nStatus == ERROR_MORE_DATA); + return nStatus == ERROR_SUCCESS; + } + return false; +} + +static bool isUncRoot(const QString &server) +{ + QString localPath = QDir::toNativeSeparators(server); + QStringList parts = localPath.split(QLatin1Char('\\'), QString::SkipEmptyParts); + return localPath.startsWith(QLatin1String("\\\\")) && parts.count() <= 1; +} + +static bool isUncPath(const QString &path) +{ + // Starts with // or \\, but not \\. or //. + return (path.startsWith(QLatin1String("//")) + || path.startsWith(QLatin1String("\\\\"))) + && (path.size() > 2 && path.at(2) != QLatin1Char('.')); +} + +static bool isRelativePath(const QString &path) +{ + return !(path.startsWith(QLatin1Char('/')) + || (path.length() >= 2 + && ((path.at(0).isLetter() && path.at(1) == QLatin1Char(':')) + || (path.at(0) == QLatin1Char('/') && path.at(1) == QLatin1Char('/'))))); // drive, e.g. a: +} + +static QString fixIfRelativeUncPath(const QString &path) +{ + if (isRelativePath(path)) { + QString currentPath = QDir::currentPath() + QLatin1Char('/'); + if (currentPath.startsWith(QLatin1String("//"))) + return QString(path).prepend(currentPath); + } + return path; +} + +// can be //server or //server/share +static bool uncShareExists(const QString &server) +{ + QStringList parts = server.split(QLatin1Char('\\'), QString::SkipEmptyParts); + if (parts.count()) { + QStringList shares; + if (QFSFileEnginePrivate::uncListSharesOnServer(QLatin1String("\\\\") + parts.at(0), &shares)) { + if (parts.count() >= 2) + return shares.contains(parts.at(1), Qt::CaseInsensitive); + else + return true; + } + } + return false; +} + +#if !defined(Q_OS_WINCE) +// If you change this function, remember to also change the UNICODE version +static QString nativeAbsoluteFilePathA(const QString &path) +{ + QString ret; + QVarLengthArray<char, MAX_PATH> buf(MAX_PATH); + char *fileName = 0; + QByteArray ba = path.toLocal8Bit(); + DWORD retLen = GetFullPathNameA(ba.constData(), buf.size(), buf.data(), &fileName); + if (retLen > (DWORD)buf.size()) { + buf.resize(retLen); + retLen = GetFullPathNameA(ba.constData(), buf.size(), buf.data(), &fileName); + } + if (retLen != 0) + ret = QString::fromLocal8Bit(buf.data(), retLen); + return ret; +} +#endif + +// If you change this function, remember to also change the NON-UNICODE version +static QString nativeAbsoluteFilePathW(const QString &path) +{ + QString ret; +#if !defined(Q_OS_WINCE) + QVarLengthArray<wchar_t, MAX_PATH> buf(MAX_PATH); + wchar_t *fileName = 0; + DWORD retLen = GetFullPathNameW((wchar_t*)path.utf16(), buf.size(), buf.data(), &fileName); + if (retLen > (DWORD)buf.size()) { + buf.resize(retLen); + retLen = GetFullPathNameW((wchar_t*)path.utf16(), buf.size(), buf.data(), &fileName); + } + if (retLen != 0) + ret = QString::fromUtf16((unsigned short *)buf.data(), retLen); +#else + if (path.startsWith(QLatin1String("/")) || path.startsWith(QLatin1String("\\"))) + ret = QDir::toNativeSeparators(path); + else + ret = QDir::toNativeSeparators(QDir::cleanPath(qfsPrivateCurrentDir + QLatin1Char('/') + path)); +#endif + return ret; +} + +static QString nativeAbsoluteFilePath(const QString &path) +{ + QString absPath = QT_WA_INLINE(nativeAbsoluteFilePathW(path), nativeAbsoluteFilePathA(path)); + // 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. + int i = path.size() - 1; + while (i >= 0 && path.at(i) == QLatin1Char(' ')) --i; + int extraws = path.size() - 1 - i; + if (extraws >= 0) { + while (extraws) { + absPath.append(QLatin1Char(' ')); + --extraws; + } + } + return absPath; +} + +QByteArray QFSFileEnginePrivate::win95Name(const QString &path) +{ + QString ret(path); + if(path.length() > 1 && path[0] == QLatin1Char('/') && path[1] == QLatin1Char('/')) { + // Win95 cannot handle slash-slash needs slosh-slosh. + ret[0] = QLatin1Char('\\'); + ret[1] = QLatin1Char('\\'); + int n = ret.indexOf(QLatin1Char('/')); + if(n >= 0) + ret[n] = QLatin1Char('\\'); + } else if(path.length() > 3 && path[2] == QLatin1Char('/') && path[3] == QLatin1Char('/')) { + ret[2] = QLatin1Char('\\'); + ret.remove(3, 1); + int n = ret.indexOf(QLatin1Char('/')); + if(n >= 0) + ret[n] = QLatin1Char('\\'); + } + return ret.toLocal8Bit(); +} + +/*! + \internal +*/ +QString QFSFileEnginePrivate::longFileName(const QString &path) +{ + if (path.startsWith(QLatin1String("\\\\.\\"))) + return path; + + QString absPath = nativeAbsoluteFilePath(path); +#if !defined(Q_OS_WINCE) + QString prefix = QLatin1String("\\\\?\\"); + if (isUncPath(absPath)) { + prefix = QLatin1String("\\\\?\\UNC\\"); + absPath.remove(0, 2); + } + return prefix + absPath; +#else + return absPath; +#endif +} + +/* + \internal +*/ +void QFSFileEnginePrivate::nativeInitFileName() +{ + QT_WA({ + QString path = longFileName(QDir::toNativeSeparators(fixIfRelativeUncPath(filePath))); + nativeFilePath = QByteArray((const char *)path.utf16(), path.size() * 2 + 1); + }, { + QString path = fixIfRelativeUncPath(filePath); + nativeFilePath = win95Name(path).replace('/', '\\'); + }); +} + +/* + \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. + QT_WA({ + fileHandle = CreateFileW((TCHAR *)nativeFilePath.constData(), + accessRights, + shareMode, + &securityAtts, + creationDisp, + FILE_ATTRIBUTE_NORMAL, + NULL); + }, { + fileHandle = CreateFileA(nativeFilePath.constData(), + 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; + if ((fileHandle == INVALID_HANDLE_VALUE || !CloseHandle(fileHandle)) +#ifdef Q_USE_DEPRECATED_MAP_API + && (fileMapHandle == INVALID_HANDLE_VALUE || !CloseHandle(fileMapHandle)) +#endif + ) { + q->setError(QFile::UnspecifiedError, qt_error_string()); + ok = false; + } + fileHandle = INVALID_HANDLE_VALUE; + cachedFd = -1; // gets closed by CloseHandle above + + 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(); + + // Buffered stdlib mode. + if (fh) { + QT_OFF_T oldPos = QT_FTELL(fh); + QT_FSEEK(fh, 0, SEEK_END); + QT_OFF_T fileSize = QT_FTELL(fh); + QT_FSEEK(fh, oldPos, SEEK_SET); + return qint64(fileSize); + } + + // Not-open mode, where the file name is known: We'll check the + // file system directly. + if (openMode == QIODevice::NotOpen && !nativeFilePath.isEmpty()) { + bool ok = false; + WIN32_FILE_ATTRIBUTE_DATA attribData; + QT_WA({ + ok = ::GetFileAttributesExW((TCHAR *)nativeFilePath.constData(), + GetFileExInfoStandard, &attribData); + } , { + ok = ::GetFileAttributesExA(nativeFilePath.constData(), + GetFileExInfoStandard, &attribData); + }); + if (ok) { + qint64 size = attribData.nFileSizeHigh; + size <<= 32; + size += attribData.nFileSizeLow; + return size; + } + thatQ->setError(QFile::UnspecifiedError, qt_error_string()); + return 0; + } + + // Unbuffed stdio mode. + if(fd != -1) { +#if !defined(Q_OS_WINCE) + HANDLE handle = (HANDLE)_get_osfhandle(fd); + if (handle != INVALID_HANDLE_VALUE) { + BY_HANDLE_FILE_INFORMATION fileInfo; + if (GetFileInformationByHandle(handle, &fileInfo)) { + qint64 size = fileInfo.nFileSizeHigh; + size <<= 32; + size += fileInfo.nFileSizeLow; + return size; + } + } +#endif + thatQ->setError(QFile::UnspecifiedError, qt_error_string()); + return 0; + } + + // Windows native mode. + if (fileHandle == INVALID_HANDLE_VALUE) + return 0; + + BY_HANDLE_FILE_INFORMATION fileInfo; + if (!GetFileInformationByHandle(fileHandle, &fileInfo)) { + thatQ->setError(QFile::UnspecifiedError, qt_error_string()); + return 0; + } + + qint64 size = fileInfo.nFileSizeHigh; + size <<= 32; + size += fileInfo.nFileSizeLow; + return 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(QT_NO_LIBRARY) + QFSFileEnginePrivate::resolveLibs(); + if (!ptrSetFilePointerEx) { +#endif + DWORD newFilePointer = SetFilePointer(fileHandle, 0, NULL, FILE_CURRENT); + if (newFilePointer == 0xFFFFFFFF) { + thatQ->setError(QFile::UnspecifiedError, qt_error_string()); + return 0; + } + + // Note: returns <4GB; does not work with large files. This is the + // case for MOC, UIC, qmake and other bootstrapped tools, and for + // Win9x/ME. + return qint64(newFilePointer); +#if !defined(QT_NO_LIBRARY) + } + + // This approach supports large files. + LARGE_INTEGER currentFilePos; + LARGE_INTEGER offset; + offset.LowPart = 0; + offset.HighPart = 0; + if (!ptrSetFilePointerEx(fileHandle, offset, ¤tFilePos, FILE_CURRENT)) { + thatQ->setError(QFile::UnspecifiedError, qt_error_string()); + return 0; + } + + return qint64(currentFilePos.QuadPart); +#endif +} + +/* + \internal +*/ +bool QFSFileEnginePrivate::nativeSeek(qint64 pos) +{ + Q_Q(const QFSFileEngine); + QFSFileEngine *thatQ = const_cast<QFSFileEngine *>(q); + + if (fh || fd != -1) { + // stdlib / stdio mode. + return seekFdFh(pos); + } + +#if !defined(QT_NO_LIBRARY) + QFSFileEnginePrivate::resolveLibs(); + if (!ptrSetFilePointerEx) { +#endif + LONG seekToPos = LONG(pos); // <- lossy + DWORD newFilePointer = SetFilePointer(fileHandle, seekToPos, NULL, FILE_BEGIN); + if (newFilePointer == 0xFFFFFFFF) { + thatQ->setError(QFile::UnspecifiedError, qt_error_string()); + return false; + } + + // Note: does not work with large files. This is the case for MOC, + // UIC, qmake and other bootstrapped tools, and for Win9x/ME. + return true; +#if !defined(QT_NO_LIBRARY) + } + + // This approach supports large files. + LARGE_INTEGER currentFilePos; + LARGE_INTEGER offset; + offset.LowPart = (unsigned int)(quint64(pos) & Q_UINT64_C(0xffffffff)); + offset.HighPart = (unsigned int)((quint64(pos) >> 32) & Q_UINT64_C(0xffffffff)); + if (ptrSetFilePointerEx(fileHandle, offset, ¤tFilePos, FILE_BEGIN) == 0) { + thatQ->setError(QFile::UnspecifiedError, 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) + // stdlib / Windows native mode. + if (fh || fileHandle != INVALID_HANDLE_VALUE) { + if (fh == stdin || fh == stdout || fh == stderr) + return true; + + HANDLE handle = fileHandle; + if (fileHandle == INVALID_HANDLE_VALUE) { + // Rare case: using QFile::open(FILE*) to open a pipe. + handle = (HANDLE)_get_osfhandle(QT_FILENO(fh)); + return false; + } + + DWORD fileType = GetFileType(handle); + return fileType == FILE_TYPE_PIPE; + } + + // stdio mode. + if (fd != -1) + return isSequentialFdFh(); +#endif + return false; +} + +/*! + \reimp +*/ +bool QFSFileEngine::remove() +{ + Q_D(QFSFileEngine); + QT_WA({ + return ::DeleteFileW((TCHAR*)QFSFileEnginePrivate::longFileName(d->filePath).utf16()) != 0; + } , { + return ::DeleteFileA(QFSFileEnginePrivate::win95Name(d->filePath)) != 0; + }); +} + +/*! + \reimp +*/ +bool QFSFileEngine::copy(const QString ©Name) +{ + Q_D(QFSFileEngine); + QT_WA({ + return ::CopyFileW((TCHAR*)QFSFileEnginePrivate::longFileName(d->filePath).utf16(), + (TCHAR*)QFSFileEnginePrivate::longFileName(copyName).utf16(), true) != 0; + } , { + return ::CopyFileA(QFSFileEnginePrivate::win95Name(d->filePath), + QFSFileEnginePrivate::win95Name(copyName), true) != 0; + }); +} + +/*! + \reimp +*/ +bool QFSFileEngine::rename(const QString &newName) +{ + Q_D(QFSFileEngine); + QT_WA({ + return ::MoveFileW((TCHAR*)QFSFileEnginePrivate::longFileName(d->filePath).utf16(), + (TCHAR*)QFSFileEnginePrivate::longFileName(newName).utf16()) != 0; + } , { + return ::MoveFileA(QFSFileEnginePrivate::win95Name(d->filePath), + QFSFileEnginePrivate::win95Name(newName)) != 0; + }); +} + +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 + QT_WA({ + return ::CreateDirectoryW((TCHAR*)QFSFileEnginePrivate::longFileName(path).utf16(), 0); + } , { + return ::CreateDirectoryA(QFSFileEnginePrivate::win95Name(QFileInfo(path).absoluteFilePath()), 0); + }); +} + +/*! + \reimp +*/ +static inline bool rmDir(const QString &path) +{ + QT_WA({ + return ::RemoveDirectoryW((TCHAR*)QFSFileEnginePrivate::longFileName(path).utf16()); + } , { + return ::RemoveDirectoryA(QFSFileEnginePrivate::win95Name(QFileInfo(path).absoluteFilePath())); + }); +} + +/*! + \reimp +*/ +static inline bool isDirPath(const QString &dirPath, bool *existed) +{ + QString path = dirPath; + if (path.length() == 2 &&path.at(1) == QLatin1Char(':')) + path += QLatin1Char('\\'); + + DWORD fileAttrib = INVALID_FILE_ATTRIBUTES; + QT_WA({ + fileAttrib = ::GetFileAttributesW((TCHAR*)QFSFileEnginePrivate::longFileName(path).utf16()); + } , { + fileAttrib = ::GetFileAttributesA(QFSFileEnginePrivate::win95Name(QFileInfo(path).absoluteFilePath())); + }); + + if (existed) + *existed = fileAttrib != INVALID_FILE_ATTRIBUTES; + + if (fileAttrib == INVALID_FILE_ATTRIBUTES) + return false; + + return fileAttrib & FILE_ATTRIBUTE_DIRECTORY; +} + +/*! + \reimp +*/ +bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const +{ + QString dirName = name; + if (createParentDirectories) { + 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) && !existed) { + if (!mkDir(chunk)) + return false; + } + } + } + return true; + } + return mkDir(name); +} + +/*! + \reimp +*/ +bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const +{ + QString dirName = name; + if (recurseParentDirectories) { + 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(name); +} + +/*! + \reimp +*/ +bool QFSFileEngine::caseSensitive() const +{ + return false; +} + +/*! + 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() +*/ +bool QFSFileEngine::setCurrentPath(const QString &path) +{ + if (!QDir(path).exists()) + return false; + +#if !defined(Q_OS_WINCE) + int r; + QT_WA({ + r = ::SetCurrentDirectoryW((WCHAR*)path.utf16()); + } , { + r = ::SetCurrentDirectoryA(QFSFileEnginePrivate::win95Name(path)); + }); + return r != 0; +#else + qfsPrivateCurrentDir = QFSFileEnginePrivate::longFileName(path); + return true; +#endif +} + +/*! + 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() +*/ +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) { + QT_WA({ + TCHAR buf[PATH_MAX]; + ::_wgetdcwd(drv, buf, PATH_MAX); + ret.setUtf16((ushort*)buf, uint(::wcslen(buf))); + }, { + char buf[PATH_MAX]; + ::_getdcwd(drv, buf, PATH_MAX); + ret = QString::fromLatin1(buf); + }); + } + } + if (ret.isEmpty()) { + //just the pwd + QT_WA({ + DWORD size = 0; + WCHAR currentName[PATH_MAX]; + size = ::GetCurrentDirectoryW(PATH_MAX, currentName); + if (size !=0) { + if (size > PATH_MAX) { + WCHAR * newCurrentName = new WCHAR[size]; + if (::GetCurrentDirectoryW(PATH_MAX, newCurrentName) != 0) + ret = QString::fromUtf16((ushort*)newCurrentName); + delete [] newCurrentName; + } else { + ret = QString::fromUtf16((ushort*)currentName); + } + } + } , { + DWORD size = 0; + char currentName[PATH_MAX]; + size = ::GetCurrentDirectoryA(PATH_MAX, currentName); + if (size !=0) + ret = QString::fromLocal8Bit(currentName); + }); + } + if (ret.length() >= 2 && ret[1] == QLatin1Char(':')) + ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters. + return QDir::fromNativeSeparators(ret); +#else + Q_UNUSED(fileName); + if (qfsPrivateCurrentDir.isEmpty()) + qfsPrivateCurrentDir = QCoreApplication::applicationDirPath(); + + return QDir::fromNativeSeparators(qfsPrivateCurrentDir); +#endif +} + +/*! + Returns the home path of the current user. + + \sa rootPath() +*/ +QString QFSFileEngine::homePath() +{ + QString ret; +#if !defined(QT_NO_LIBRARY) + QT_WA ( + { + QFSFileEnginePrivate::resolveLibs(); + if (ptrOpenProcessToken && ptrGetUserProfileDirectoryW) { + HANDLE hnd = ::GetCurrentProcess(); + HANDLE token = 0; + BOOL ok = ::ptrOpenProcessToken(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::fromUtf16((ushort*)userDirectory); + + delete [] userDirectory; + } + ::CloseHandle(token); + } + } + } + , + { + // GetUserProfileDirectory is only available from NT 4.0, + // so fall through for Win98 and friends version. + }) +#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 = QString::fromLatin1("\\My Documents"); + if (!QFile::exists(ret)) +#endif + ret = rootPath(); + } + } + } + } + return QDir::fromNativeSeparators(ret); +} + +/*! + Returns the root path. + + \sa homePath() +*/ +QString QFSFileEngine::rootPath() +{ +#if defined(Q_OS_WINCE) + QString ret = QString::fromLatin1("/"); +#elif defined(Q_FS_FAT) + QString ret = QString::fromLatin1(qgetenv("SystemDrive").constData()); + if(ret.isEmpty()) + ret = QLatin1String("c:"); + ret += QLatin1String("/"); +#elif defined(Q_OS_OS2EMX) + char dir[4]; + _abspath(dir, QLatin1String("/"), _MAX_PATH); + QString ret(dir); +#endif + return ret; +} + +/*! + Returns the temporary path (i.e., a path in which it is safe to store + temporary files). +*/ +QString QFSFileEngine::tempPath() +{ + QString ret; + int success; + QT_WA({ + wchar_t tempPath[MAX_PATH]; + success = GetTempPathW(MAX_PATH, tempPath); + ret = QString::fromUtf16((ushort*)tempPath); + } , { + char tempPath[MAX_PATH]; + success = GetTempPathA(MAX_PATH, tempPath); + ret = QString::fromLocal8Bit(tempPath); + }); + if (ret.isEmpty() || !success) { +#if !defined(Q_OS_WINCE) + ret = QString::fromLatin1("c:/tmp"); +#else + ret = QString::fromLatin1("\\Temp"); +#endif + } else { + ret = QDir::fromNativeSeparators(ret); + while (ret.at(ret.length()-1) == QLatin1Char('/')) + ret = ret.left(ret.length()-1); + } + return ret; +} + +/*! + 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.). +*/ +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[4]; + + qstrcpy(driveName, "A:/"); + + while(driveBits) { + if(driveBits & 1) + ret.append(QString::fromLatin1(driveName).toUpper()); + driveName[0]++; + driveBits = driveBits >> 1; + } + return ret; +#else + ret.append(QString::fromLatin1("/").toUpper()); + return ret; +#endif +} + +bool QFSFileEnginePrivate::doStat() const +{ + if (!tried_stat) { + tried_stat = true; + could_stat = false; + + if (filePath.isEmpty()) + return could_stat; + QString fname = filePath.endsWith(QLatin1String(".lnk")) ? readLink(filePath) : filePath; + fname = fixIfRelativeUncPath(fname); + + UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX); + + if (fd != -1) { +#if !defined(Q_OS_WINCE) + HANDLE fh = (HANDLE)_get_osfhandle(fd); + if (fh != INVALID_HANDLE_VALUE) { + BY_HANDLE_FILE_INFORMATION fileInfo; + if (GetFileInformationByHandle(fh, &fileInfo)) { + could_stat = true; + fileAttrib = fileInfo.dwFileAttributes; + } + } +#else + DWORD tmpAttributes = GetFileAttributesW((TCHAR*)QFSFileEnginePrivate::longFileName(fname).utf16()); + if (tmpAttributes != -1) { + fileAttrib = tmpAttributes; + could_stat = true; + } else { + return false; + } +#endif + } else { + QT_WA({ + fileAttrib = GetFileAttributesW((TCHAR*)QFSFileEnginePrivate::longFileName(fname).utf16()); + } , { + fileAttrib = GetFileAttributesA(QFSFileEnginePrivate::win95Name(QFileInfo(fname).absoluteFilePath())); + }); + could_stat = fileAttrib != INVALID_FILE_ATTRIBUTES; + if (!could_stat) { +#if !defined(Q_OS_WINCE) + if (!fname.isEmpty() && fname.at(0).isLetter() && fname.mid(1, fname.length()) == QLatin1String(":/")) { + // an empty drive ?? + DWORD drivesBitmask = ::GetLogicalDrives(); + int drivebit = 1 << (fname.at(0).toUpper().unicode() - QLatin1Char('A').unicode()); + if (drivesBitmask & drivebit) { + fileAttrib = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM; + could_stat = true; + } + } else { +#endif + QString path = QDir::toNativeSeparators(fname); + bool is_dir = false; + if (path.startsWith(QLatin1String("\\\\"))) { + // UNC - stat doesn't work for all cases (Windows bug) + int s = path.indexOf(path.at(0),2); + if (s > 0) { + // "\\server\..." + s = path.indexOf(path.at(0),s+1); + if (s > 0) { + // "\\server\share\..." + if (s == path.size() - 1) { + // "\\server\share\" + is_dir = true; + } else { + // "\\server\share\notfound" + } + } else { + // "\\server\share" + is_dir = true; + } + } else { + // "\\server" + is_dir = true; + } + } + if (is_dir && uncShareExists(path)) { + // looks like a UNC dir, is a dir. + fileAttrib = FILE_ATTRIBUTE_DIRECTORY; + could_stat = true; + } +#if !defined(Q_OS_WINCE) + } +#endif + } + } + SetErrorMode(oldmode); + } + return could_stat; +} + + +static QString readLink(const QString &link) +{ +#if !defined(Q_OS_WINCE) +#if !defined(QT_NO_LIBRARY) && !defined(Q_CC_MWERKS) + QString ret; + QT_WA({ + bool neededCoInit = false; + IShellLink *psl; // pointer to IShellLink i/f + HRESULT hres; + WIN32_FIND_DATA wfd; + TCHAR szGotPath[MAX_PATH]; + // Get pointer to the IShellLink interface. + 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.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::fromUtf16((ushort*)szGotPath); + } + ppf->Release(); + } + psl->Release(); + } + if(neededCoInit) + CoUninitialize(); + } , { + bool neededCoInit = false; + IShellLinkA *psl; // pointer to IShellLink i/f + HRESULT hres; + WIN32_FIND_DATAA wfd; + char szGotPath[MAX_PATH]; + // Get pointer to the IShellLink interface. + + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + IID_IShellLinkA, (LPVOID *)&psl); + + if(hres == CO_E_NOTINITIALIZED) { // COM was not initialized + neededCoInit = true; + CoInitialize(NULL); + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + IID_IShellLinkA, (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)QFileInfo(link).absoluteFilePath().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((char*)szGotPath, MAX_PATH, &wfd, SLGP_UNCPRIORITY) == NOERROR) + ret = QString::fromLocal8Bit(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).absoluteFilePath().replace(QLatin1Char('/'),QLatin1Char('\\')).utf16(), target, MAX_PATH)) { + result = QString::fromUtf16(reinterpret_cast<const ushort *> (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 +} + +/*! + \internal +*/ +QString QFSFileEnginePrivate::getLink() const +{ + return readLink(filePath); +} + +/*! + \reimp +*/ +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 + + QT_WA({ + HRESULT hres; + IShellLink *psl; + bool neededCoInit = false; + + 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((TCHAR*)linkName.utf16(), TRUE); + if (SUCCEEDED(hres)) + ret = true; + ppf->Release(); + } + } + } + psl->Release(); + } + if(neededCoInit) + CoUninitialize(); + } , { + // the SetPath() call _sometimes_ changes the current path and when it does it sometimes + // does not let us change it back unless we call currentPath() many times. + QString cwd = currentPath(); + HRESULT hres; + IShellLinkA *psl; + bool neededCoInit = false; + + 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)) { + currentPath(); + hres = psl->SetPath((char *)QString::fromLocal8Bit(QFSFileEnginePrivate::win95Name(fileName(AbsoluteName))).utf16()); + currentPath(); + if (SUCCEEDED(hres)) { + hres = psl->SetWorkingDirectory((char *)QString::fromLocal8Bit(QFSFileEnginePrivate::win95Name(fileName(AbsolutePathName))).utf16()); + currentPath(); + if (SUCCEEDED(hres)) { + IPersistFile *ppf; + hres = psl->QueryInterface(IID_IPersistFile, (void **)&ppf); + if (SUCCEEDED(hres)) { + currentPath(); + hres = ppf->Save((LPCOLESTR)linkName.utf16(), TRUE); + currentPath(); + if (SUCCEEDED(hres)) + ret = true; + ppf->Release(); + } + } + psl->Release(); + } + } + if(neededCoInit) + CoUninitialize(); + setCurrentPath(cwd); + }); + 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('"')); + return SUCCEEDED(SHCreateShortcut((wchar_t*)linkName.utf16(), (wchar_t*)orgName.utf16())); +#endif // Q_OS_WINCE +} + +/*! + \internal +*/ +QAbstractFileEngine::FileFlags QFSFileEnginePrivate::getPermissions() const +{ + QAbstractFileEngine::FileFlags ret = 0; + +#if !defined(QT_NO_LIBRARY) + if((qt_ntfs_permission_lookup > 0) && ((QSysInfo::WindowsVersion&QSysInfo::WV_NT_based) > QSysInfo::WV_NT)) { + PSID pOwner = 0; + PSID pGroup = 0; + PACL pDacl; + PSECURITY_DESCRIPTOR pSD; + ACCESS_MASK access_mask; + + enum { ReadMask = 0x00000001, WriteMask = 0x00000002, ExecMask = 0x00000020 }; + resolveLibs(); + if(ptrGetNamedSecurityInfoW && ptrAllocateAndInitializeSid && ptrBuildTrusteeWithSidW && ptrGetEffectiveRightsFromAclW && ptrFreeSid) { + + QString fname = filePath.endsWith(QLatin1String(".lnk")) ? readLink(filePath) : filePath; + 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) { + TRUSTEE_W trustee; + { //user + if(ptrGetEffectiveRightsFromAclW(pDacl, ¤tUserTrusteeW, &access_mask) != ERROR_SUCCESS) + access_mask = (ACCESS_MASK)-1; + if(access_mask & ReadMask) + ret |= QAbstractFileEngine::ReadUserPerm; + if(access_mask & WriteMask) + ret |= QAbstractFileEngine::WriteUserPerm; + if(access_mask & ExecMask) + ret |= QAbstractFileEngine::ExeUserPerm; + } + { //owner + ptrBuildTrusteeWithSidW(&trustee, pOwner); + if(ptrGetEffectiveRightsFromAclW(pDacl, &trustee, &access_mask) != ERROR_SUCCESS) + access_mask = (ACCESS_MASK)-1; + if(access_mask & ReadMask) + ret |= QAbstractFileEngine::ReadOwnerPerm; + if(access_mask & WriteMask) + ret |= QAbstractFileEngine::WriteOwnerPerm; + if(access_mask & ExecMask) + ret |= QAbstractFileEngine::ExeOwnerPerm; + } + { //group + ptrBuildTrusteeWithSidW(&trustee, pGroup); + if(ptrGetEffectiveRightsFromAclW(pDacl, &trustee, &access_mask) != ERROR_SUCCESS) + access_mask = (ACCESS_MASK)-1; + if(access_mask & ReadMask) + ret |= QAbstractFileEngine::ReadGroupPerm; + if(access_mask & WriteMask) + ret |= QAbstractFileEngine::WriteGroupPerm; + if(access_mask & ExecMask) + ret |= QAbstractFileEngine::ExeGroupPerm; + } + { //other (world) + // Create SID 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(&trustee, pWorld); + if(ptrGetEffectiveRightsFromAclW(pDacl, &trustee, &access_mask) != ERROR_SUCCESS) + access_mask = (ACCESS_MASK)-1; // ### + if(access_mask & ReadMask) + ret |= QAbstractFileEngine::ReadOtherPerm; + if(access_mask & WriteMask) + ret |= QAbstractFileEngine::WriteOtherPerm; + if(access_mask & ExecMask) + ret |= QAbstractFileEngine::ExeOtherPerm; + } + ptrFreeSid(pWorld); + } + LocalFree(pSD); + } + } + } else +#endif + { + //### what to do with permissions if we don't use ntfs or are not on a NT system + // for now just add all permissions and what about exe missions ?? + // also qt_ntfs_permission_lookup is now not set by defualt ... should it ? + ret |= QAbstractFileEngine::ReadOtherPerm | QAbstractFileEngine::ReadGroupPerm + | QAbstractFileEngine::ReadOwnerPerm | QAbstractFileEngine::ReadUserPerm + | QAbstractFileEngine::WriteUserPerm | QAbstractFileEngine::WriteOwnerPerm + | QAbstractFileEngine::WriteGroupPerm | QAbstractFileEngine::WriteOtherPerm; + } + + if (doStat()) { + if (ret & (QAbstractFileEngine::WriteOwnerPerm | QAbstractFileEngine::WriteUserPerm | + QAbstractFileEngine::WriteGroupPerm | QAbstractFileEngine::WriteOtherPerm)) { + if (fileAttrib & FILE_ATTRIBUTE_READONLY) + ret &= ~(QAbstractFileEngine::WriteOwnerPerm | QAbstractFileEngine::WriteUserPerm | + QAbstractFileEngine::WriteGroupPerm | QAbstractFileEngine::WriteOtherPerm); + } + + QString ext = filePath.right(4).toLower(); + if (ext == QLatin1String(".exe") || ext == QLatin1String(".com") || ext == QLatin1String(".bat") || + ext == QLatin1String(".pif") || ext == QLatin1String(".cmd") || (fileAttrib & FILE_ATTRIBUTE_DIRECTORY)) + ret |= QAbstractFileEngine::ExeOwnerPerm | QAbstractFileEngine::ExeGroupPerm | + QAbstractFileEngine::ExeOtherPerm | QAbstractFileEngine::ExeUserPerm; + } + return ret; +} + +/*! + \reimp +*/ +QAbstractFileEngine::FileFlags QFSFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const +{ + Q_D(const QFSFileEngine); + QAbstractFileEngine::FileFlags ret = 0; + // Force a stat, so that we're guaranteed to get up-to-date results + if (type & QAbstractFileEngine::FileFlag(QAbstractFileEngine::Refresh)) { + d->tried_stat = 0; + } + + if (type & PermsMask) { + ret |= d->getPermissions(); + // ### Workaround pascals ### above. Since we always set all properties to true + // we need to disable read and exec access if the file does not exists + if (d->doStat()) + ret |= ExistsFlag; + else + ret &= 0x2222; + } + if (type & TypesMask) { + if (d->filePath.endsWith(QLatin1String(".lnk"))) { + ret |= LinkType; + QString l = readLink(d->filePath); + if (!l.isEmpty()) { + if (isDirPath(l, 0)) + ret |= DirectoryType; + else + ret |= FileType; + } + } else if (d->doStat()) { + if (d->fileAttrib & FILE_ATTRIBUTE_DIRECTORY) { + ret |= DirectoryType; + } else { + ret |= FileType; + } + } + } + if (type & FlagsMask) { + if(d->doStat()) { + ret |= QAbstractFileEngine::FileFlags(ExistsFlag | LocalDiskFlag); + if (d->fileAttrib & FILE_ATTRIBUTE_HIDDEN) + ret |= HiddenFlag; + if (d->filePath == QLatin1String("/") || (d->filePath.at(0).isLetter() && d->filePath.mid(1,d->filePath.length()) == QLatin1String(":/")) + || isUncRoot(d->filePath)) { + ret |= RootFlag; + ret &= ~HiddenFlag; + } + } + } + return ret; +} + +/*! + \reimp +*/ +QString QFSFileEngine::fileName(FileName file) const +{ + Q_D(const QFSFileEngine); + if(file == BaseName) { + int slash = d->filePath.lastIndexOf(QLatin1Char('/')); + if(slash == -1) { + int colon = d->filePath.lastIndexOf(QLatin1Char(':')); + if(colon != -1) + return d->filePath.mid(colon + 1); + return d->filePath; + } + return d->filePath.mid(slash + 1); + } else if(file == PathName) { + if(!d->filePath.size()) + return d->filePath; + + int slash = d->filePath.lastIndexOf(QLatin1Char('/')); + if(slash == -1) { + if(d->filePath.length() >= 2 && d->filePath.at(1) == QLatin1Char(':')) + return d->filePath.left(2); + return QString::fromLatin1("."); + } else { + if(!slash) + return QString::fromLatin1("/"); + if(slash == 2 && d->filePath.length() >= 2 && d->filePath.at(1) == QLatin1Char(':')) + slash++; + return d->filePath.left(slash); + } + } else if(file == AbsoluteName || file == AbsolutePathName) { + QString ret; + + if (!isRelativePath()) { +#if !defined(Q_OS_WINCE) + if (d->filePath.size() > 2 && d->filePath.at(1) == QLatin1Char(':') + && d->filePath.at(2) != QLatin1Char('/') || // It's a drive-relative path, so Z:a.txt -> Z:\currentpath\a.txt + d->filePath.startsWith(QLatin1Char('/')) // It's a absolute path to the current drive, so \a.txt -> Z:\a.txt + ) { + ret = QDir::fromNativeSeparators(nativeAbsoluteFilePath(d->filePath)); + } else { + ret = d->filePath; + } +#else + ret = d->filePath; +#endif + } else { + ret = QDir::cleanPath(QDir::currentPath() + QLatin1Char('/') + d->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(); + + QString ret = QFSFileEnginePrivate::canonicalized(fileName(AbsoluteName)); + if (!ret.isEmpty() && file == CanonicalPathName) { + int slash = ret.lastIndexOf(QLatin1Char('/')); + if (slash == -1) + ret = QDir::currentPath(); + else if (slash == 0) + ret = QLatin1String("/"); + ret = ret.left(slash); + } + return ret; + } else if(file == LinkName) { + return QDir::fromNativeSeparators(d->getLink()); + } else if(file == BundleName) { + return QString(); + } + return d->filePath; +} + +/*! + \reimp +*/ +bool QFSFileEngine::isRelativePath() const +{ + Q_D(const QFSFileEngine); + return !(d->filePath.startsWith(QLatin1Char('/')) + || (d->filePath.length() >= 2 + && ((d->filePath.at(0).isLetter() && d->filePath.at(1) == QLatin1Char(':')) + || (d->filePath.at(0) == QLatin1Char('/') && d->filePath.at(1) == QLatin1Char('/'))))); // drive, e.g. a: +} + +/*! + \reimp +*/ +uint QFSFileEngine::ownerId(FileOwner /*own*/) const +{ + static const uint nobodyID = (uint) -2; + return nobodyID; +} + +/*! + \reimp +*/ +QString QFSFileEngine::owner(FileOwner own) const +{ +#if !defined(QT_NO_LIBRARY) + Q_D(const QFSFileEngine); + if((qt_ntfs_permission_lookup > 0) && ((QSysInfo::WindowsVersion&QSysInfo::WV_NT_based) > QSysInfo::WV_NT)) { + PSID pOwner = 0; + PSECURITY_DESCRIPTOR pSD; + QString name; + QFSFileEnginePrivate::resolveLibs(); + + if(ptrGetNamedSecurityInfoW && ptrLookupAccountSidW) { + if(ptrGetNamedSecurityInfoW((wchar_t*)d->filePath.utf16(), SE_FILE_OBJECT, + own == OwnerGroup ? GROUP_SECURITY_INFORMATION : OWNER_SECURITY_INFORMATION, + NULL, &pOwner, NULL, NULL, &pSD) == ERROR_SUCCESS) { + DWORD lowner = 0, ldomain = 0; + SID_NAME_USE use; + // First call, to determine size of the strings (with '\0'). + ptrLookupAccountSidW(NULL, pOwner, NULL, &lowner, NULL, &ldomain, (SID_NAME_USE*)&use); + wchar_t *owner = new wchar_t[lowner]; + wchar_t *domain = new wchar_t[ldomain]; + // Second call, size is without '\0' + if(ptrLookupAccountSidW(NULL, pOwner, (LPWSTR)owner, &lowner, + (LPWSTR)domain, &ldomain, (SID_NAME_USE*)&use)) { + name = QString::fromUtf16((ushort*)owner); + } + LocalFree(pSD); + delete [] owner; + delete [] domain; + } + } + return name; + } +#else + Q_UNUSED(own); +#endif + return QString(QLatin1String("")); +} + +/*! + \reimp +*/ +bool QFSFileEngine::setPermissions(uint perms) +{ + Q_D(QFSFileEngine); + bool ret = false; + int mode = 0; + + if (perms & QFile::ReadOwner || perms & QFile::ReadUser || perms & QFile::ReadGroup || perms & QFile::ReadOther) + mode |= _S_IREAD; + if (perms & QFile::WriteOwner || perms & QFile::WriteUser || perms & QFile::WriteGroup || perms & QFile::WriteOther) + mode |= _S_IWRITE; + + if (mode == 0) // not supported + return false; + +#if !defined(Q_OS_WINCE) + QT_WA({ + ret = ::_wchmod((TCHAR*)d->filePath.utf16(), mode) == 0; + } , { + ret = ::_chmod(d->filePath.toLocal8Bit(), mode) == 0; + }); +#else + ret = ::_wchmod((TCHAR*)d->longFileName(d->filePath).utf16(), mode); +#endif + return ret; +} + +/*! + \reimp +*/ +bool QFSFileEngine::setSize(qint64 size) +{ + Q_D(QFSFileEngine); + + if (d->fileHandle != INVALID_HANDLE_VALUE || d->fd != -1) { + // resize open file + HANDLE fh = d->fileHandle; +#if !defined(Q_OS_WINCE) + if (fh == INVALID_HANDLE_VALUE) + 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->nativeFilePath.isEmpty()) { + // resize file on disk + QFile file(d->filePath); + if (file.open(QFile::ReadWrite)) { + return file.resize(size); + } + } + return false; +} + + +static inline QDateTime fileTimeToQDateTime(const FILETIME *time) +{ + QDateTime ret; + if (QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based || QSysInfo::WindowsVersion & QSysInfo::WV_CE_based) { + // SystemTimeToTzSpecificLocalTime is not available on Win98/ME so we have to pull it off ourselves. + 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 { +#ifndef Q_OS_WINCE + 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; +} + +/*! + \reimp +*/ +QDateTime QFSFileEngine::fileTime(FileTime time) const +{ + Q_D(const QFSFileEngine); + QDateTime ret; + if (d->fd != -1) { +#if !defined(Q_OS_WINCE) + HANDLE fh = (HANDLE)_get_osfhandle(d->fd); + if (fh != INVALID_HANDLE_VALUE) { + FILETIME creationTime, lastAccessTime, lastWriteTime; + if (GetFileTime(fh, &creationTime, &lastAccessTime, &lastWriteTime)) { + if(time == CreationTime) + ret = fileTimeToQDateTime(&creationTime); + else if(time == ModificationTime) + ret = fileTimeToQDateTime(&lastWriteTime); + else if(time == AccessTime) + ret = fileTimeToQDateTime(&lastAccessTime); + } + } +#endif + } else { + bool ok = false; + WIN32_FILE_ATTRIBUTE_DATA attribData; + QT_WA({ + ok = ::GetFileAttributesExW((TCHAR*)QFSFileEnginePrivate::longFileName(d->filePath).utf16(), GetFileExInfoStandard, &attribData); + } , { + ok = ::GetFileAttributesExA(QFSFileEnginePrivate::win95Name(QFileInfo(d->filePath).absoluteFilePath()), GetFileExInfoStandard, &attribData); + }); + if (ok) { + if(time == CreationTime) + ret = fileTimeToQDateTime(&attribData.ftCreationTime); + else if(time == ModificationTime) + ret = fileTimeToQDateTime(&attribData.ftLastWriteTime); + else if(time == AccessTime) + ret = fileTimeToQDateTime(&attribData.ftLastAccessTime); + } + } + return ret; +} + +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()); + return 0; + } + if (offset == 0 && size == 0) { + q->setError(QFile::UnspecifiedError, qt_error_string()); + return 0; + } + + + // 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)); +#else + #ifdef Q_USE_DEPRECATED_MAP_API + nativeClose(); + if (fileMapHandle == INVALID_HANDLE_VALUE) { + fileMapHandle = CreateFileForMappingW((TCHAR *)nativeFilePath.constData(), + GENERIC_READ | (openMode & QIODevice::WriteOnly ? GENERIC_WRITE : 0), + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + } + handle = fileMapHandle; + #endif + if (handle == INVALID_HANDLE_VALUE && fh) + return 0; +#endif + + // first create the file mapping handle + HANDLE mapHandle = 0; + DWORD protection = (openMode & QIODevice::WriteOnly) ? PAGE_READWRITE : PAGE_READONLY; + QT_WA({ + mapHandle = ::CreateFileMappingW(handle, 0, protection, + 0, 0, 0); + },{ + mapHandle = ::CreateFileMappingA(handle, 0, protection, + 0, 0, 0); + }); + if (mapHandle == NULL) { + q->setError(QFile::PermissionsError, qt_error_string()); +#ifdef Q_USE_DEPRECATED_MAP_API + mapHandleClose(); +#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); + int mask = sysinfo.dwAllocationGranularity - 1; + int 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] = QPair<int, HANDLE>(extra, mapHandle); + 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); +#ifdef Q_USE_DEPRECATED_MAP_API + mapHandleClose(); +#endif + return 0; +} + +bool QFSFileEnginePrivate::unmap(uchar *ptr) +{ + Q_Q(QFSFileEngine); + if (!maps.contains(ptr)) { + q->setError(QFile::PermissionsError, qt_error_string()); + return false; + } + uchar *start = ptr - maps[ptr].first; + if (!UnmapViewOfFile(start)) { + q->setError(QFile::PermissionsError, qt_error_string()); + return false; + } + + if (!CloseHandle((HANDLE)maps[ptr].second)) { + q->setError(QFile::UnspecifiedError, qt_error_string()); + return false; + } + maps.remove(ptr); + +#ifdef Q_USE_DEPRECATED_MAP_API + mapHandleClose(); +#endif + return true; +} + +#ifdef Q_USE_DEPRECATED_MAP_API +void QFSFileEnginePrivate::mapHandleClose() +{ + if (maps.isEmpty()) { + CloseHandle(fileMapHandle); + fileMapHandle = INVALID_HANDLE_VALUE; + } +} +#endif +QT_END_NAMESPACE diff --git a/src/corelib/io/qiodevice.cpp b/src/corelib/io/qiodevice.cpp new file mode 100644 index 0000000..fe3ceff --- /dev/null +++ b/src/corelib/io/qiodevice.cpp @@ -0,0 +1,1763 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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> + +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 + +#ifndef QIODEVICE_BUFFERSIZE +#define QIODEVICE_BUFFERSIZE Q_INT64_C(16384) +#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), accessMode(Unset) +{ +} + +/*! \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; for example, access to a QBuffer is always + unbuffered. 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, 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, className()); +#endif +} + +/*! \internal +*/ +QIODevice::QIODevice(QIODevicePrivate &dd, QObject *parent) + : QObject(dd, parent) +{ +} +#endif + + +/*! + Destructs the QIODevice object. +*/ +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) +{ +#if defined QIODEVICE_DEBUG + printf("%p QIODevice::setOpenMode(0x%x)\n", this, int(openMode)); +#endif + d_func()->openMode = openMode; + d_func()->accessMode = QIODevicePrivate::Unset; +} + +/*! + 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; +#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(); +} + +/*! + 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) +{ + if (d_func()->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; + } + + Q_D(QIODevice); +#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 && !d->buffer.isEmpty()) { + // When seeking forwards, we need to pop bytes off the front of the + // buffer. + do { + int bytesToSkip = int(qMin<qint64>(offset, INT_MAX)); + d->buffer.skip(bytesToSkip); + offset -= bytesToSkip; + } while (offset > 0); + } else if (offset < 0) { + // 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(); + } +#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); +} + +/*! + 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); + CHECK_READABLE(read, qint64(-1)); + CHECK_MAXLEN(read, qint64(-1)); + +#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 + const bool sequential = d->isSequential(); + + // Short circuit for getChar() + if (maxSize == 1) { + int chint = d->buffer.getChar(); + if (chint != -1) { + char c = char(uchar(chint)); + if (c == '\r' && (d->openMode & Text)) { + d->buffer.ungetChar(c); + } else { + if (data) + *data = c; + if (!sequential) + ++d->pos; +#if defined QIODEVICE_DEBUG + printf("%p \tread 0x%hhx (%c) returning 1 (shortcut)\n", this, + int(c), isprint(c) ? c : '?'); +#endif + return qint64(1); + } + } + } + + qint64 readSoFar = 0; + bool moreToRead = true; + do { + int lastReadChunkSize = 0; + + // Try reading from the buffer. + if (!d->buffer.isEmpty()) { + lastReadChunkSize = d->buffer.read(data + readSoFar, maxSize - readSoFar); + readSoFar += lastReadChunkSize; + if (!sequential) + d->pos += 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->openMode & Unbuffered) == 0 && maxSize < QIODEVICE_BUFFERSIZE) { + // In buffered mode, we try to fill up the QIODevice buffer before + // we do anything else. + int bytesToBuffer = qMax(maxSize - readSoFar, QIODEVICE_BUFFERSIZE); + char *writePointer = d->buffer.reserve(bytesToBuffer); + + // Make sure the device is positioned correctly. + if (d->pos != d->devicePos && !sequential && !seek(d->pos)) + return qint64(-1); + qint64 readFromDevice = readData(writePointer, bytesToBuffer); + d->buffer.chop(bytesToBuffer - (readFromDevice < 0 ? 0 : int(readFromDevice))); + + if (readFromDevice > 0) { + if (!sequential) + d->devicePos += readFromDevice; +#if defined QIODEVICE_DEBUG + printf("%p \treading %d from device into buffer\n", this, int(readFromDevice)); +#endif + + if (readFromDevice < bytesToBuffer) + d->buffer.truncate(readFromDevice < 0 ? 0 : int(readFromDevice)); + if (!d->buffer.isEmpty()) { + lastReadChunkSize = d->buffer.read(data + readSoFar, maxSize - readSoFar); + readSoFar += lastReadChunkSize; + if (!sequential) + d->pos += 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 (readSoFar < maxSize) { + // Make sure the device is positioned correctly. + if (d->pos != d->devicePos && !sequential && !seek(d->pos)) + return qint64(-1); + qint64 readFromDevice = readData(data + readSoFar, maxSize - readSoFar); +#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) { + moreToRead = false; + } else { + // see if we read as much data as we asked for + if (readFromDevice < maxSize - readSoFar) + moreToRead = false; + + lastReadChunkSize += int(readFromDevice); + readSoFar += readFromDevice; + if (!sequential) { + d->pos += readFromDevice; + d->devicePos += readFromDevice; + } + } + } else { + moreToRead = false; + } + + if (readSoFar && d->openMode & Text) { + char *readPtr = data + readSoFar - lastReadChunkSize; + const char *endPtr = data + readSoFar; + + 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; + } + + // 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); +#endif + return readSoFar; +} + +/*! + \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); + CHECK_MAXLEN(read, QByteArray()); + QByteArray tmp; + qint64 readSoFar = 0; + char buffer[4096]; +#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 + + do { + qint64 bytesToRead = qMin(int(maxSize - readSoFar), int(sizeof(buffer))); + qint64 readBytes = read(buffer, bytesToRead); + if (readBytes <= 0) + break; + tmp.append(buffer, (int) readBytes); + readSoFar += readBytes; + } while (readSoFar < maxSize && bytesAvailable() > 0); + + return tmp; +} + +/*! + \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 tmp; + if (d->isSequential() || size() == 0) { + // Read it in chunks, bytesAvailable() is unreliable for sequential + // devices. + const int chunkSize = 4096; + qint64 totalRead = 0; + forever { + tmp.resize(tmp.size() + chunkSize); + qint64 readBytes = read(tmp.data() + totalRead, chunkSize); + tmp.chop(chunkSize - (readBytes < 0 ? 0 : readBytes)); + if (readBytes <= 0) + return tmp; + totalRead += readBytes; + } + } else { + // Read it all in one go. + tmp.resize(int(bytesAvailable())); + qint64 readBytes = read(tmp.data(), tmp.size()); + tmp.resize(readBytes < 0 ? 0 : int(readBytes)); + } + return tmp; +} + +/*! + 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 what it could 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); + CHECK_MAXLEN(readLine, QByteArray()); + QByteArray tmp; + const int BufferGrowth = 4096; + qint64 readSoFar = 0; + qint64 readBytes = 0; + +#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 + + do { + if (maxSize != 0) + tmp.resize(int(readSoFar + qMin(int(maxSize), BufferGrowth))); + else + tmp.resize(int(readSoFar + BufferGrowth)); + readBytes = readLine(tmp.data() + readSoFar, tmp.size() - readSoFar); + if (readBytes <= 0) + break; + + readSoFar += readBytes; + } while ((!maxSize || readSoFar < maxSize) && + readSoFar + 1 == tmp.size() && // +1 due to the ending null + tmp.at(readSoFar - 1) != '\n'); + + if (readSoFar == 0 && readBytes == -1) + tmp.clear(); // return Null if we found an error + else + tmp.resize(int(readSoFar)); + return tmp; +} + +/*! + 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; +} + +/*! + 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; +} + +/*! \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) +{ + Q_D(QIODevice); + const OpenMode openMode = d->openMode; + if (!(openMode & ReadOnly)) { + if (openMode == NotOpen) + qWarning("QIODevice::getChar: Closed device"); + else + qWarning("QIODevice::getChar: WriteOnly device"); + return false; + } + + // Shortcut for QIODevice::read(c, 1) + QRingBuffer *buffer = &d->buffer; + const int chint = buffer->getChar(); + if (chint != -1) { + char ch = char(uchar(chint)); + if ((openMode & Text) && ch == '\r') { + buffer->ungetChar(ch); + } else { + if (c) + *c = ch; + if (!d->isSequential()) + ++d->pos; + return true; + } + } + + // Fall back to read(). + char ch; + if (read(&ch, 1) == 1) { + if (c) + *c = ch; + return true; + } + return false; +} + +/*! + \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) +{ + qint64 readBytes = read(data, maxSize); + int i = readBytes; + while (i > 0) + ungetChar(data[i-- - 1]); + return readBytes; +} + +/*! + \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) +{ + QByteArray result = read(maxSize); + int i = result.size(); + const char *data = result.constData(); + while (i > 0) + ungetChar(data[i-- - 1]); + return result; +} + +/*! + Blocks until 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 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, this function should return -1 if + there can never be more bytes available (for example: socket + closed, pipe closed, sub-process finished). + + This function is called by QIODevice. Reimplement this function + when creating a subclass of QIODevice. + + \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. + + \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 0000000..76d47ca --- /dev/null +++ b/src/corelib/io/qiodevice.h @@ -0,0 +1,253 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIODEVICE_H +#define QIODEVICE_H + +#ifndef QT_NO_QOBJECT +#include <QtCore/qobject.h> +#else +#include <QtCore/qobjectdefs.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 + 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(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 0000000..b996225 --- /dev/null +++ b/src/corelib/io/qiodevice_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 + +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; + + QRingBuffer buffer; + qint64 pos; + qint64 devicePos; + bool baseReadLineDataCalled; + + 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; + } + + +#ifdef QT_NO_QOBJECT + QIODevice *q_ptr; +#endif +}; + +QT_END_NAMESPACE + +#endif // QIODEVICE_P_H diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp new file mode 100644 index 0000000..65ffec7 --- /dev/null +++ b/src/corelib/io/qprocess.cpp @@ -0,0 +1,1853 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 <qdatetime.h> +#include <qcoreapplication.h> +#include <qsocketnotifier.h> +#include <qtimer.h> + +#ifdef Q_WS_WIN +#include <private/qwineventnotifier_p.h> +#endif + +#ifndef QT_NO_PROCESS + +QT_BEGIN_NAMESPACE + +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; +} + +/*! + \class QProcess + + \brief The QProcess class is used to start external programs and + to communicate with them. + + \ingroup io + \ingroup misc + \mainclass + \reentrant + + To start a process, pass the name and command line arguments of + the program you want to run as arguments to start(). For example: + + \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. + + \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 setReadChannelMode() +*/ + +/*! + \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")); +#if defined(QPROCESS_DEBUG) && !defined(Q_OS_WINCE) + qDebug("QProcessPrivate::canWrite(), failed to write (%s)", strerror(errno)); +#endif + emit q->error(processError); + return false; + } + +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::canWrite(), wrote %d bytes to the process input", int(written)); +#endif + + 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 setReadChannelMode(), 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 readChannelMode(), 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 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; +} + +/*! + 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 + + \sa environment(), systemEnvironment() +*/ +void QProcess::setEnvironment(const QStringList &environment) +{ + Q_D(QProcess); + d->environment = environment; +} + +/*! + Returns the environment that QProcess will use when starting a + process, or an empty QStringList if no environment has been set + using setEnvironment(). 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 setEnvironment(), systemEnvironment() +*/ +QStringList QProcess::environment() 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) { + QTime 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 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 finished(), waitForStarted(), waitForReadyRead(), waitForBytesWritten() +*/ +bool QProcess::waitForFinished(int msecs) +{ + Q_D(QProcess); + if (d->processState == QProcess::NotRunning) + return false; + if (d->processState == QProcess::Starting) { + QTime 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 program \a program in a new process, passing the + command line arguments in \a arguments. The OpenMode is set to \a + mode. QProcess will immediately enter the Starting state. If the + process starts successfully, QProcess will emit started(); + otherwise, error() will be emitted. + + Note that arguments that contain spaces are not passed to the + process as separate arguments. + + \bold{Windows:} Arguments that contain spaces are wrapped in quotes. + + \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. + + \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. \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 + + 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); + + 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(). + + \note Terminating running processes from other processes will typically + cause a panic in Symbian due to platform security. + + \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. + + \note Killing running processes from other processes will typically + cause a panic in Symbian due to platform security. + + \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 by the calling + process. + + On Windows, arguments that contain spaces are wrapped in quotes. +*/ +int QProcess::execute(const QString &program, const QStringList &arguments) +{ + QProcess process; + process.setReadChannelMode(ForwardedChannels); + process.start(program, arguments); + process.waitForFinished(-1); + return process.exitCode(); +} + +/*! + \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); + process.waitForFinished(-1); + return process.exitCode(); +} + +/*! + 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 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. +*/ +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); + + QString prog = args.first(); + args.removeFirst(); + + return QProcessPrivate::startDetached(prog, args); +} + +QT_BEGIN_INCLUDE_NAMESPACE +#ifdef Q_OS_MAC +# include <crt_externs.h> +# define environ (*_NSGetEnviron()) +#elif defined(Q_OS_WINCE) || defined(Q_OS_SYMBIAN) + 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 + + \sa environment(), setEnvironment() +*/ +QStringList QProcess::systemEnvironment() +{ + QStringList tmp; + char *entry = 0; + int count = 0; + while ((entry = environ[count++])) + tmp << QString::fromLocal8Bit(entry); + return tmp; +} + +/*! + \typedef Q_PID + \relates QProcess + + Typedef for the identifiers used to represent processes on the underlying + platform. On Unix, 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 0000000..ae40de3 --- /dev/null +++ b/src/corelib/io/qprocess.h @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROCESS_H +#define QPROCESS_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +#ifndef QT_NO_PROCESS + +#if (!defined(Q_OS_WIN32) && !defined(Q_OS_WINCE) && !defined(Q_OS_SYMBIAN)) || defined(qdoc) +typedef qint64 Q_PID; +#elif defined(Q_OS_SYMBIAN) +QT_END_NAMESPACE +# include <e32std.h> +QT_BEGIN_NAMESPACE +typedef TProcessId Q_PID; +#else +QT_END_NAMESPACE +typedef struct _PROCESS_INFORMATION *Q_PID; +QT_BEGIN_NAMESPACE +#endif + +class QProcessPrivate; + +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); + + QString workingDirectory() const; + void setWorkingDirectory(const QString &dir); + + void setEnvironment(const QStringList &environment); + QStringList environment() 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 0000000..554c477 --- /dev/null +++ b/src/corelib/io/qprocess_p.h @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 "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; + +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; + QStringList 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 0000000..ae7f51a --- /dev/null +++ b/src/corelib/io/qprocess_symbian.cpp @@ -0,0 +1,1003 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the $MODULE$ of the Qt Toolkit. +** +** $TROLLTECH_DUAL_EMBEDDED_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 <e32base.h> +#include <e32std.h> +#include <stdio.h> +#include "qplatformdefs.h" + +#include "qstring.h" +#include "qprocess.h" +#include "qprocess_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 CProcessActive : public CActive +{ +public: + static CProcessActive* construct(QProcess* process, + RProcess** proc, + int serial, + int deathPipe); + + virtual ~CProcessActive(); + + void start(); + void stop(); + + bool error(); + +protected: + + // From CActive + void RunL(); + TInt RunError(TInt aError); + void DoCancel(); + + CProcessActive(); + +private: + + QProcess* process; + RProcess** pproc; + int serial; + int deathPipe; + bool errorValue; +}; + +// Active object to communicate synchronously with process manager thread +class CProcessManagerMediator : public CActive +{ +public: + static CProcessManagerMediator* construct(); + + virtual ~CProcessManagerMediator(); + + bool add(CProcessActive* processObserver); + void remove(CProcessActive* processObserver); + void terminate(); + +protected: + + enum Commands { + ENoCommand, + EAdd, + ERemove, + ETerminate + }; + + // From CActive + void RunL(); + TInt RunError(TInt aError); + void DoCancel(); + + CProcessManagerMediator(); + + bool notify(CProcessActive* processObserver, Commands command); + +private: + CProcessActive* currentObserver; + Commands currentCommand; + + RThread processManagerThread; +}; + +// Process manager manages child process death listeners +class QProcessManager +{ +public: + QProcessManager(); + ~QProcessManager(); + + void startThread(); + + TInt run(void* param); + bool add(QProcess *process); + void remove(QProcess *process); + + inline void setMediator(CProcessManagerMediator* newMediator) {mediator = newMediator;}; + +private: + inline void lock() {managerMutex.Wait();}; + inline void unlock() {managerMutex.Signal();}; + + QMap<int, CProcessActive *> children; + CProcessManagerMediator* 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, 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; + } else { + commandLine += QLatin1Char(' ') + tmp; + } + } +} + +static TInt qt_create_symbian_process(RProcess **proc, const QString &programName, const QStringList &arguments) +{ + RProcess* newProc = NULL; + newProc = new RProcess(); + + if (!newProc) { + return KErrNoMemory; + } + + QString commandLine; + qt_create_symbian_commandline(arguments, 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(QChar('\\')); + int index2 = programName.lastIndexOf(QChar('/')); + index = qMax(index, index2); + + if(index != -1 && programName.length() >= index){ + QString strippedName; + strippedName = programName.mid(index+1); + QPROCESS_DEBUG_PRINT("qt_create_symbian_process() Process '%s' not found, try 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 +CProcessActive* CProcessActive::construct(QProcess* process, + RProcess** proc, + int serial, + int deathPipe) +{ + QPROCESS_ASSERT((process || proc || *proc), + EProcessActiveNullParameter, + "CProcessActive::construct(): process (0x%x), proc (0x%x) or *proc == NULL, not creating an instance", process, proc) + + CProcessActive* newInstance = new CProcessActive(); + + if (!newInstance) { + QPROCESS_DEBUG_PRINT("CProcessActive::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 +CProcessActive::CProcessActive() + : CActive(CActive::EPriorityStandard) +{ + // Nothing to do +} + +// Called from ProcessManagerThread +CProcessActive::~CProcessActive() +{ + process = NULL; + pproc = NULL; +} + +// Called from ProcessManagerThread +void CProcessActive::start() +{ + if (qt_rprocess_running(*pproc)) { + CActiveScheduler::Add(this); + (*pproc)->Logon(iStatus); + SetActive(); + QPROCESS_DEBUG_PRINT("CProcessActive::start(): Started monitoring for process exit."); + } else { + QPROCESS_DEBUG_PRINT("CProcessActive::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 CProcessActive::stop() +{ + QPROCESS_DEBUG_PRINT("CProcessActive::stop()"); + + // Remove this from scheduler (also cancels the request) + Deque(); +} + +bool CProcessActive::error() +{ + return errorValue; +} + +// Called from ProcessManagerThread +void CProcessActive::RunL() +{ + // If this method gets executed, the monitored process has died + + // Notify main thread + qt_native_write(deathPipe, "", 1); + QPROCESS_DEBUG_PRINT("CProcessActive::RunL() sending death notice to %d", deathPipe); +} + +// Called from ProcessManagerThread +TInt CProcessActive::RunError(TInt aError) +{ + Q_UNUSED(aError); + // Handle RunL leave (should never happen) + QPROCESS_ASSERT(0, EProcessActiveRunError, "CProcessActive::RunError(): Should never get here!") + return 0; +} + +// Called from ProcessManagerThread +void CProcessActive::DoCancel() +{ + QPROCESS_DEBUG_PRINT("CProcessActive::DoCancel()"); + + if (qt_rprocess_running(*pproc)) { + (*pproc)->LogonCancel(iStatus); + QPROCESS_DEBUG_PRINT("CProcessActive::DoCancel(): Stopped monitoring for process exit."); + } else { + QPROCESS_DEBUG_PRINT("CProcessActive::DoCancel(): Process doesn't exist"); + } +} + + +// Called from ProcessManagerThread +CProcessManagerMediator* CProcessManagerMediator::construct() +{ + CProcessManagerMediator* newInstance = new CProcessManagerMediator; + TInt err(KErrNone); + + newInstance->currentCommand = ENoCommand; + newInstance->currentObserver = NULL; + + if (newInstance) { + err = newInstance->processManagerThread.Open(newInstance->processManagerThread.Id()); + QPROCESS_ASSERT((err == KErrNone), + EProcessManagerMediatorThreadOpenFailed, + "CProcessManagerMediator::construct(): Failed to open processManagerThread (err:%d)", err) + } else { + QPROCESS_ASSERT(0, + EProcessManagerMediatorCreationFailed, + "CProcessManagerMediator::construct(): Failed to open construct mediator") + } + + // Activate mediator + CActiveScheduler::Add(newInstance); + newInstance->iStatus = KRequestPending; + newInstance->SetActive(); + QPROCESS_DEBUG_PRINT("CProcessManagerMediator::construct(): new instance successfully created and activated"); + + return newInstance; +} + +// Called from ProcessManagerThread +CProcessManagerMediator::CProcessManagerMediator() + : CActive(CActive::EPriorityStandard) +{ + // Nothing to do +} + +// Called from ProcessManagerThread +CProcessManagerMediator::~CProcessManagerMediator() +{ + processManagerThread.Close(); + currentCommand = ENoCommand; + currentObserver = NULL; +} + +// Called from main thread +bool CProcessManagerMediator::add(CProcessActive* processObserver) +{ + QPROCESS_DEBUG_PRINT("CProcessManagerMediator::add()"); + return notify(processObserver, EAdd); +} + +// Called from main thread +void CProcessManagerMediator::remove(CProcessActive* processObserver) +{ + QPROCESS_DEBUG_PRINT("CProcessManagerMediator::remove()"); + notify(processObserver, ERemove); +} + +// Called from main thread +void CProcessManagerMediator::terminate() +{ + QPROCESS_DEBUG_PRINT("CProcessManagerMediator::terminate()"); + notify(NULL, ETerminate); +} + +// Called from main thread +bool CProcessManagerMediator::notify(CProcessActive* processObserver, Commands command) +{ + bool success(true); + + QPROCESS_DEBUG_PRINT("CProcessManagerMediator::Notify(): Command: %d, processObserver: 0x%x", command, processObserver); + + QPROCESS_ASSERT((command == ETerminate || processObserver), + EProcessManagerMediatorNullObserver, + "CProcessManagerMediator::Notify(): NULL processObserver not allowed for command: %d", command) + + QPROCESS_ASSERT(IsActive(), + EProcessManagerMediatorInactive, + "CProcessManagerMediator::Notify(): Mediator is not active!") + + QPROCESS_ASSERT(iStatus==KRequestPending, + EProcessManagerMediatorNotPending, + "CProcessManagerMediator::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("CProcessManagerMediator::Notify(): Waiting process manager to complete..."); + User::WaitForRequest(pmStatus); + QPROCESS_DEBUG_PRINT("CProcessManagerMediator::Notify(): Wait over"); + + if (currentObserver) { + success = !(currentObserver->error()); + QPROCESS_DEBUG_PRINT("CProcessManagerMediator::Notify(): success = %d", success); + } + + currentObserver = NULL; + currentCommand = ENoCommand; + + return success; +} + +// Called from ProcessManagerThread +void CProcessManagerMediator::RunL() +{ + QPROCESS_DEBUG_PRINT("CProcessManagerMediator::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, + "CProcessManagerMediator::RunL(): Invalid command!") + break; + } + + iStatus = KRequestPending; + SetActive(); + + // Notify main thread that we are done + RThread::Rendezvous(KErrNone); +} + +// Called from ProcessManagerThread +TInt CProcessManagerMediator::RunError(TInt aError) +{ + Q_UNUSED(aError); + // Handle RunL leave (should never happen) + QPROCESS_ASSERT(0, + EProcessManagerMediatorRunError, + "CProcessManagerMediator::RunError(): Should never get here!") + return 0; +} + +// Called from ProcessManagerThread +void CProcessManagerMediator::DoCancel() +{ + QPROCESS_DEBUG_PRINT("CProcessManagerMediator::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 CActiveScheduler(); + + 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(CProcessManagerMediator::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()"); + // Cancel death listening for all child processes + if (mediator) { + QMap<int, CProcessActive *>::Iterator it = children.begin(); + while (it != children.end()) { + // Remove all monitors + CProcessActive *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 CProcessActive 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); + + CProcessActive* newActive = + CProcessActive::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 CProcessActive to ProcessManager"); + delete newActive; + } + } + + unlock(); + + return false; +} + +void QProcessManager::remove(QProcess *process) +{ + QPROCESS_ASSERT(process, + EProcessManagerNullParam, + "QProcessManager::remove(): Failed to remove CProcessActive from ProcessManager - NULL process") + + lock(); + + int serial = process->d_func()->serial; + CProcessActive *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); + + if (err == KErrNone) { + pid = symbianProcess->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: %d", this, (TUint)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, zero bytes are always available + return 0; +} + +qint64 QProcessPrivate::bytesAvailableFromStderr() const +{ + // In Symbian, zero bytes are always available + return 0; +} + +qint64 QProcessPrivate::readFromStdout(char *data, qint64 maxlen) +{ + Q_UNUSED(data); + Q_UNUSED(maxlen); + // In Symbian, zero bytes are always read + return 0; +} + +qint64 QProcessPrivate::readFromStderr(char *data, qint64 maxlen) +{ + Q_UNUSED(data); + Q_UNUSED(maxlen); + // In Symbian, zero bytes are always read + return 0; +} + +qint64 QProcessPrivate::writeToStdin(const char *data, qint64 maxlen) +{ + Q_UNUSED(data); + Q_UNUSED(maxlen); + // In Symbian, zero bytes are always written + return 0; +} + +void QProcessPrivate::terminateProcess() +{ + // Not allowed by platform security - will panic kern-exec 46 if process has been started. + // 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() +{ + // Not allowed by platform security - will panic kern-exec 46 if process has been started. + // 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 = 0; + TRequestStatus logonStatus = 0; + bool timeoutOccurred = false; + + // Logon to process to observe its death + if (qt_rprocess_running(symbianProcess)) { + symbianProcess->Logon(logonStatus); + + // Create timer + RTimer timer; + timer.CreateLocal(); + TTimeIntervalMicroSeconds32 interval(msecs*1000); + timer.After(timerStatus, interval); + + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForFinished() - Waiting..."); + User::WaitForRequest(logonStatus, timerStatus); + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForFinished() - Wait completed"); + + if (timerStatus == KErrNone) { + timeoutOccurred = true; + } + + timer.Cancel(); + timer.Close(); + + symbianProcess->LogonCancel(logonStatus); + + // Eat cancel request completion so that it won't mess up main thread scheduling later + User::WaitForRequest(logonStatus, timerStatus); + } 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::fromUtf16(catName.Ptr()); +#endif + } else { + QPROCESS_DEBUG_PRINT("QProcessPrivate::waitForDeadChild() not dead!"); + } + } + + return true; +} + +void QProcessPrivate::_q_notified() +{ + // Nothing to do in Symbian +} + +/*! \internal + */ +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); + + if (err == KErrNone) { + if (pid) { + *pid = (qint64)newProc->Id(); + } + + newProc->Resume(); + newProc->Close(); + 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 0000000..37173c8 --- /dev/null +++ b/src/corelib/io/qprocess_unix.cpp @@ -0,0 +1,1380 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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" + +#ifdef Q_OS_MAC +#include <private/qcore_mac_p.h> +#endif + +#include <private/qcoreapplication_p.h> +#include <private/qthread_p.h> +#include <qdatetime.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qlist.h> +#include <qmap.h> +#include <qmutex.h> +#include <qsemaphore.h> +#include <qsocketnotifier.h> +#include <qthread.h> + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +QT_BEGIN_NAMESPACE + +#ifdef Q_OS_INTEGRITY +static inline char *strdup(const char *data) +{ + return qstrdup(data); +} +#endif + +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); + 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); + return ret; +} + +static void qt_native_close(int fd) +{ + int ret; + do { + ret = ::close(fd); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_sigaction(int signum, const struct sigaction *act, + struct sigaction *oldact) +{ + int ret; + do { + ret = ::sigaction(signum, act, oldact); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_dup2(int oldfd, int newfd) +{ + int ret; + do { + ret = ::dup2(oldfd, newfd); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_chdir(const char *path) +{ + int ret; + do { + ret = ::chdir(path); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_execve(const char *filename, char *const argv[], + char *const envp[]) +{ + int ret; + do { + ret = ::execve(filename, argv, envp); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_execv(const char *path, char *const argv[]) +{ + int ret; + do { + ret = ::execv(path, argv); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_execvp(const char *file, char *const argv[]) +{ + int ret; + do { + ret = ::execvp(file, argv); + } while (ret == -1 && errno == EINTR); +} + +static int qt_qprocess_deadChild_pipe[2]; +static void (*qt_sa_old_sigchld_handler)(int) = 0; +static void qt_sa_sigchld_handler(int signum) +{ + qt_native_write(qt_qprocess_deadChild_pipe[1], "", 1); +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "*** SIGCHLD\n"); +#endif + + if (qt_sa_old_sigchld_handler && qt_sa_old_sigchld_handler != SIG_IGN) + qt_sa_old_sigchld_handler(signum); +} + +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; + QMap<int, QProcessInfo *> children; +}; + +Q_GLOBAL_STATIC(QProcessManager, 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. + ::pipe(qt_qprocess_deadChild_pipe); + ::fcntl(qt_qprocess_deadChild_pipe[0], F_SETFD, FD_CLOEXEC); + ::fcntl(qt_qprocess_deadChild_pipe[1], F_SETFD, FD_CLOEXEC); + ::fcntl(qt_qprocess_deadChild_pipe[0], F_SETFL, + ::fcntl(qt_qprocess_deadChild_pipe[0], F_GETFL) | O_NONBLOCK); + ::fcntl(qt_qprocess_deadChild_pipe[1], F_SETFL, + ::fcntl(qt_qprocess_deadChild_pipe[1], F_GETFL) | O_NONBLOCK); + + // set up the SIGCHLD handler, which writes a single byte to the dead + // child pipe every time a child dies. + struct sigaction oldAction; + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = qt_sa_sigchld_handler; + action.sa_flags = SA_NOCLDSTOP; + qt_native_sigaction(SIGCHLD, &action, &oldAction); + if (oldAction.sa_handler != qt_sa_sigchld_handler) + qt_sa_old_sigchld_handler = oldAction.sa_handler; +} + +QProcessManager::~QProcessManager() +{ + // notify the thread that we're shutting down. + qt_native_write(qt_qprocess_deadChild_pipe[1], "@", 1); + qt_native_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_native_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 oldAction; + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = qt_sa_old_sigchld_handler; + action.sa_flags = SA_NOCLDSTOP; + qt_native_sigaction(SIGCHLD, &action, &oldAction); + if (oldAction.sa_handler != qt_sa_sigchld_handler) { + qt_native_sigaction(SIGCHLD, &oldAction, 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_native_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). + QMap<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_native_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.value(serial); + if (!info) + return; + +#if defined (QPROCESS_DEBUG) + qDebug() << "QProcessManager::remove() removing pid" << info->pid << "process" << info->process; +#endif + + children.remove(serial); + delete info; +} + +void QProcessManager::lock() +{ + mutex.lock(); +} + +void QProcessManager::unlock() +{ + mutex.unlock(); +} + +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]); +#ifdef Q_OS_IRIX + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, pipe) == -1) { + qWarning("QProcessPrivate::createPipe: Cannot create pipe %p: %s", + pipe, qPrintable(qt_error_string(errno))); + } +#else + if (::pipe(pipe) != 0) { + qWarning("QProcessPrivate::createPipe: Cannot create pipe %p: %s", + pipe, qPrintable(qt_error_string(errno))); + } +#endif + ::fcntl(pipe[0], F_SETFD, FD_CLOEXEC); + ::fcntl(pipe[1], F_SETFD, FD_CLOEXEC); +} + +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; + } +} + +/* + 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_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_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 QStringList &environment, int *envc) +{ + // 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 QString libraryPathString = QLatin1String(libraryPath); + QStringList env = environment; + QStringList matches = env.filter( + QRegExp(QLatin1Char('^') + libraryPathString + QLatin1Char('='))); + const QString envLibraryPath = QString::fromLocal8Bit(::getenv(libraryPath)); + if (matches.isEmpty() && !envLibraryPath.isEmpty()) { + QString entry = libraryPathString; + entry += QLatin1Char('='); + entry += envLibraryPath; + env << libraryPathString + QLatin1Char('=') + envLibraryPath; + } + + char **envp = new char *[env.count() + 1]; + envp[env.count()] = 0; + + for (int j = 0; j < env.count(); ++j) { + QString item = env.at(j); + envp[j] = ::strdup(item.toLocal8Bit().constData()); + } + + *envc = env.count(); + 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 + qt_create_pipe(childStartedPipe); + if (threadData->eventDispatcher) { + startupSocketNotifier = new QSocketNotifier(childStartedPipe[0], + QSocketNotifier::Read, q); + QObject::connect(startupSocketNotifier, SIGNAL(activated(int)), + q, SLOT(_q_startupNotification())); + } + + qt_create_pipe(deathPipe); + ::fcntl(deathPipe[0], F_SETFD, FD_CLOEXEC); + ::fcntl(deathPipe[1], F_SETFD, FD_CLOEXEC); + if (threadData->eventDispatcher) { + deathNotifier = new QSocketNotifier(deathPipe[0], + QSocketNotifier::Read, q); + QObject::connect(deathNotifier, SIGNAL(activated(int)), + q, SLOT(_q_processDied())); + } + + if (!createChannel(stdinChannel) || + !createChannel(stdoutChannel) || + !createChannel(stderrChannel)) + return; + + // 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 = _q_dupEnvironment(environment, &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", 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_native_close(childStartedPipe[1]); + childStartedPipe[1] = -1; + + if (stdinChannel.pipe[0] != -1) { + qt_native_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_native_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_native_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 + qt_native_dup2(stdinChannel.pipe[0], fileno(stdin)); + + // copy the stdout and stderr if asked to + if (processChannelMode != QProcess::ForwardedChannels) { + qt_native_dup2(stdoutChannel.pipe[1], fileno(stdout)); + + // merge stdout and stderr if asked to + if (processChannelMode == QProcess::MergedChannels) { + qt_native_dup2(fileno(stdout), fileno(stderr)); + } else { + qt_native_dup2(stderrChannel.pipe[1], fileno(stderr)); + } + } + + // make sure this fd is closed if execvp() succeeds + qt_native_close(childStartedPipe[0]); + ::fcntl(childStartedPipe[1], F_SETFD, FD_CLOEXEC); + + // enter the working directory + if (workingDir) + qt_native_chdir(workingDir); + + // this is a virtual call, and it base behavior is to do nothing. + q->setupChildProcess(); + + // execute the process + if (environment.isEmpty()) { + qt_native_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_native_execve(argv[0], argv, envp); + ++arg; + } + } else { +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "QProcessPrivate::execChild() starting %s\n", argv[0]); +#endif + qt_native_execve(argv[0], argv, envp); + } + } + + // notify failure +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "QProcessPrivate::execChild() failed, notifying parent process\n"); +#endif + qt_native_write(childStartedPipe[1], "", 1); + qt_native_close(childStartedPipe[1]); + childStartedPipe[1] = -1; +} + +bool QProcessPrivate::processStarted() +{ + char c; + int i = qt_native_read(childStartedPipe[0], &c, 1); + if (startupSocketNotifier) { + startupSocketNotifier->setEnabled(false); + startupSocketNotifier->deleteLater(); + startupSocketNotifier = 0; + } + qt_native_close(childStartedPipe[0]); + childStartedPipe[0] = -1; + +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::processStarted() == %s", i <= 0 ? "true" : "false"); +#endif + return i <= 0; +} + +qint64 QProcessPrivate::bytesAvailableFromStdout() const +{ + size_t nbytes = 0; + qint64 available = 0; + if (::ioctl(stdoutChannel.pipe[0], FIONREAD, (char *) &nbytes) >= 0) + available = (qint64) *((int *) &nbytes); +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::bytesAvailableFromStdout() == %lld", available); +#endif + return available; +} + +qint64 QProcessPrivate::bytesAvailableFromStderr() const +{ + size_t nbytes = 0; + qint64 available = 0; + if (::ioctl(stderrChannel.pipe[0], FIONREAD, (char *) &nbytes) >= 0) + available = (qint64) *((int *) &nbytes); +#if defined (QPROCESS_DEBUG) + qDebug("QProcessPrivate::bytesAvailableFromStderr() == %lld", available); +#endif + return available; +} + +qint64 QProcessPrivate::readFromStdout(char *data, qint64 maxlen) +{ + qint64 bytesRead = qt_native_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_native_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; + qt_native_sigaction(SIGPIPE, &noaction, 0); + } +} + +qint64 QProcessPrivate::writeToStdin(const char *data, qint64 maxlen) +{ + qt_ignore_sigpipe(); + + qint64 written = qt_native_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); +#endif + 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 qt_native_select(fd_set *fdread, fd_set *fdwrite, int timeout) +{ + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int ret; + do { + ret = select(FD_SETSIZE, fdread, fdwrite, 0, timeout < 0 ? 0 : &tv); + } while (ret < 0 && (errno == EINTR)); + return ret; +} + +/* + 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); + int ret; + do { + ret = qt_native_select(&fds, 0, msecs); + } while (ret < 0 && errno == EINTR); + if (ret == 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 + + QTime stopWatch; + stopWatch.start(); + + forever { + fd_set fdread; + fd_set fdwrite; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + + if (processState == QProcess::Starting) + FD_SET(childStartedPipe[0], &fdread); + + if (stdoutChannel.pipe[0] != -1) + FD_SET(stdoutChannel.pipe[0], &fdread); + if (stderrChannel.pipe[0] != -1) + FD_SET(stderrChannel.pipe[0], &fdread); + + FD_SET(deathPipe[0], &fdread); + + if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1) + FD_SET(stdinChannel.pipe[1], &fdwrite); + + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + int ret = qt_native_select(&fdread, &fdwrite, timeout); + if (ret < 0) { + if (errno == EINTR) + continue; + 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 + + QTime stopWatch; + stopWatch.start(); + + while (!writeBuffer.isEmpty()) { + fd_set fdread; + fd_set fdwrite; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + + if (processState == QProcess::Starting) + FD_SET(childStartedPipe[0], &fdread); + + if (stdoutChannel.pipe[0] != -1) + FD_SET(stdoutChannel.pipe[0], &fdread); + if (stderrChannel.pipe[0] != -1) + FD_SET(stderrChannel.pipe[0], &fdread); + + FD_SET(deathPipe[0], &fdread); + + if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1) + FD_SET(stdinChannel.pipe[1], &fdwrite); + + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + int ret = qt_native_select(&fdread, &fdwrite, timeout); + if (ret < 0) { + if (errno == EINTR) + continue; + 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 + + QTime stopWatch; + stopWatch.start(); + + forever { + fd_set fdread; + fd_set fdwrite; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + + if (processState == QProcess::Starting) + FD_SET(childStartedPipe[0], &fdread); + + if (stdoutChannel.pipe[0] != -1) + FD_SET(stdoutChannel.pipe[0], &fdread); + if (stderrChannel.pipe[0] != -1) + FD_SET(stderrChannel.pipe[0], &fdread); + + if (processState == QProcess::Running) + FD_SET(deathPipe[0], &fdread); + + if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1) + FD_SET(stdinChannel.pipe[1], &fdwrite); + + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + int ret = qt_native_select(&fdread, &fdwrite, timeout); + if (ret < 0) { + if (errno == EINTR) + continue; + 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); + + int ret; + do { + ret = qt_native_select(0, &fdwrite, msecs < 0 ? 0 : msecs) == 1; + } while (ret < 0 && errno == EINTR); + return ret == 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_native_read(deathPipe[0], &c, 1); + + // check if our process is dead + int exitStatus; + pid_t waitResult = 0; + do { + waitResult = waitpid(pid_t(pid), &exitStatus, WNOHANG); + } while ((waitResult == -1 && errno == EINTR)); + if (waitResult > 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() +{ +} + +/*! \internal + */ +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]; + ::pipe(startedPipe); + // To communicate the pid of the child + int pidPipe[2]; + ::pipe(pidPipe); + + pid_t childPid = qt_fork(); + if (childPid == 0) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + qt_native_sigaction(SIGPIPE, &noaction, 0); + + ::setsid(); + + qt_native_close(startedPipe[0]); + qt_native_close(pidPipe[0]); + + pid_t doubleForkPid = qt_fork(); + if (doubleForkPid == 0) { + ::fcntl(startedPipe[1], F_SETFD, FD_CLOEXEC); + qt_native_close(pidPipe[1]); + + if (!encodedWorkingDirectory.isEmpty()) + qt_native_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_native_execv(argv[0], argv); + } + } + } else { + QByteArray tmp = QFile::encodeName(program); + argv[0] = tmp.data(); + qt_native_execv(argv[0], argv); + } + + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + qt_native_sigaction(SIGPIPE, &noaction, 0); + + // '\1' means execv failed + char c = '\1'; + qt_native_write(startedPipe[1], &c, 1); + qt_native_close(startedPipe[1]); + ::_exit(1); + } else if (doubleForkPid == -1) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + qt_native_sigaction(SIGPIPE, &noaction, 0); + + // '\2' means internal error + char c = '\2'; + qt_native_write(startedPipe[1], &c, 1); + } + + qt_native_close(startedPipe[1]); + qt_native_write(pidPipe[1], (const char *)&doubleForkPid, sizeof(pid_t)); + qt_native_chdir("/"); + ::_exit(1); + } + + qt_native_close(startedPipe[1]); + qt_native_close(pidPipe[1]); + + if (childPid == -1) { + qt_native_close(startedPipe[0]); + qt_native_close(pidPipe[0]); + return false; + } + + char reply = '\0'; + int startResult = qt_native_read(startedPipe[0], &reply, 1); + int result; + qt_native_close(startedPipe[0]); + while (::waitpid(childPid, &result, 0) == -1 && errno == EINTR) + { } + bool success = (startResult != -1 && reply == '\0'); + if (success && pid) { + pid_t actualPid = 0; + if (qt_native_read(pidPipe[0], (char *)&actualPid, sizeof(pid_t)) == sizeof(pid_t)) { + *pid = actualPid; + } else { + *pid = 0; + } + } + qt_native_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 0000000..0e36760 --- /dev/null +++ b/src/corelib/io/qprocess_win.cpp @@ -0,0 +1,909 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 and win95FileName + + +#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; + QT_WA({ + channel.pipe[0] = + CreateFileW((TCHAR*)QFSFileEnginePrivate::longFileName(channel.file).utf16(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &secAtt, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + }, { + channel.pipe[0] = + CreateFileA(QFSFileEnginePrivate::win95Name(channel.file), + 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; + DWORD creation; + if (channel.append) + creation = OPEN_ALWAYS; + else + creation = CREATE_ALWAYS; + + QT_WA({ + channel.pipe[1] = + CreateFileW((TCHAR*)QFSFileEnginePrivate::longFileName(channel.file).utf16(), + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &secAtt, + creation, + FILE_ATTRIBUTE_NORMAL, + NULL); + }, { + channel.pipe[1] = + CreateFileA(QFSFileEnginePrivate::win95Name(channel.file), + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &secAtt, + creation, + 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(QLatin1String(" "))) + programName = QLatin1String("\"") + programName + QLatin1String("\""); + programName.replace(QLatin1String("/"), QLatin1String("\\")); + + // add the prgram as the first arg ... it works better + args = programName + QLatin1String(" "); + } + + 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("\\"); + } + args += QLatin1String(" \"") + tmp.left(i) + endQuote; + } else { + args += QLatin1Char(' ') + tmp; + } + } + return args; +} + +static QByteArray qt_create_environment(const QStringList &environment) +{ + QByteArray envlist; + if (!environment.isEmpty()) { + QStringList envStrings = environment; + int pos = 0; + // add PATH if necessary (for DLL loading) + if (envStrings.filter(QRegExp(QLatin1String("^PATH="),Qt::CaseInsensitive)).isEmpty()) { + QByteArray path = qgetenv("PATH"); + if (!path.isEmpty()) + envStrings.prepend(QString(QLatin1String("PATH=%1")).arg(QString::fromLocal8Bit(path))); + } + // add systemroot if needed + if (envStrings.filter(QRegExp(QLatin1String("^SystemRoot="),Qt::CaseInsensitive)).isEmpty()) { + QByteArray systemRoot = qgetenv("SystemRoot"); + if (!systemRoot.isEmpty()) + envStrings.prepend(QString(QLatin1String("SystemRoot=%1")).arg(QString::fromLocal8Bit(systemRoot))); + } +#ifdef UNICODE + if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) { + for (QStringList::ConstIterator it = envStrings.constBegin(); it != envStrings.constEnd(); ++it) { + QString tmp = *it; + uint tmpSize = sizeof(TCHAR) * (tmp.length()+1); + envlist.resize(envlist.size() + tmpSize); + memcpy(envlist.data()+pos, tmp.utf16(), tmpSize); + pos += tmpSize; + } + // 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; + } else +#endif // UNICODE + { + for (QStringList::ConstIterator it = envStrings.constBegin(); it != envStrings.constEnd(); it++) { + QByteArray tmp = (*it).toLocal8Bit(); + uint tmpSize = tmp.length() + 1; + envlist.resize(envlist.size() + tmpSize); + memcpy(envlist.data()+pos, tmp.data(), tmpSize); + pos += tmpSize; + } + // add the terminating 0 (actually 2, just to be on the safe side) + envlist.resize(envlist.size()+2); + 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 = qt_create_environment(environment); +#endif + +#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 + + DWORD dwCreationFlags = 0; + if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) + dwCreationFlags |= CREATE_NO_WINDOW; + +#ifdef UNICODE + if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) { +#if defined(Q_OS_WINCE) + QString fullPathProgram = program; + if (!QDir::isAbsolutePath(fullPathProgram)) + fullPathProgram = QFileInfo(fullPathProgram).absoluteFilePath(); + fullPathProgram.replace(QLatin1String("/"), QLatin1String("\\")); + success = CreateProcessW((WCHAR*)fullPathProgram.utf16(), + (WCHAR*)args.utf16(), + 0, 0, false, 0, 0, 0, 0, pid); +#else + 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 = CreateProcessW(0, (WCHAR*)args.utf16(), + 0, 0, TRUE, dwCreationFlags, + environment.isEmpty() ? 0 : envlist.data(), + workingDirectory.isEmpty() ? 0 + : (WCHAR*)QDir::toNativeSeparators(workingDirectory).utf16(), + &startupInfo, pid); +#endif + } else +#endif // UNICODE + { +#ifndef Q_OS_WINCE + STARTUPINFOA startupInfo = { sizeof( STARTUPINFOA ), 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 = CreateProcessA(0, args.toLocal8Bit().data(), + 0, 0, TRUE, dwCreationFlags, environment.isEmpty() ? 0 : envlist.data(), + workingDirectory.isEmpty() ? 0 + : QDir::toNativeSeparators(workingDirectory).toLocal8Bit().data(), + &startupInfo, pid); +#endif // Q_OS_WINCE + } +#ifndef Q_OS_WINCE + 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; + q->setErrorString(QProcess::tr("Process failed to start")); + 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 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; + +#ifdef UNICODE + if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) { +#if defined(Q_OS_WINCE) + QString fullPathProgram = program; + if (!QDir::isAbsolutePath(fullPathProgram)) + fullPathProgram.prepend(QDir::currentPath().append(QLatin1String("/"))); + fullPathProgram.replace(QLatin1String("/"), QLatin1String("\\")); + success = CreateProcessW((WCHAR*)fullPathProgram.utf16(), + (WCHAR*)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 = CreateProcessW(0, (WCHAR*)args.utf16(), + 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, 0, + workingDir.isEmpty() ? (const WCHAR *)0 : (const WCHAR *)workingDir.utf16(), + &startupInfo, &pinfo); +#endif + } else +#endif // UNICODE + { +#ifndef Q_OS_WINCE + STARTUPINFOA startupInfo = { sizeof( STARTUPINFOA ), 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 = CreateProcessA(0, args.toLocal8Bit().data(), + 0, 0, FALSE, CREATE_NEW_CONSOLE, 0, + workingDir.isEmpty() ? (LPCSTR)0 : workingDir.toLocal8Bit().constData(), + &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 0000000..a1f921e --- /dev/null +++ b/src/corelib/io/qresource.cpp @@ -0,0 +1,1460 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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" + +//#define DEBUG_RESOURCE_MATCH + +QT_BEGIN_NAMESPACE + +//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; + } +}; + +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 + \mainclass + \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 through the 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(); + for(int i = 0; i < list->size(); ++i) { + QResourceRoot *res = list->at(i); + const int node = res->findNode(file); + 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); + + bool found = false; + if(path.startsWith(QLatin1Char('/'))) { + found = 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)) { + found = true; + 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; + 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(path); + 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, 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() +{ + delete d_ptr; +} + +/*! + 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 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 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 heirarchy, + 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 + + 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. + + Use QDir::addSearchPath() with a prefix instead. +*/ + +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); +} + +/*! + Returns the current search path list. This list is consulted when + creating a relative resource. + + \sa addSearchPath() +*/ + +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 + for(int i = 0; i < name_length*2; i+=2) + ret += QChar(names[name_offset+i+1], names[name_offset+i]); + return ret; +} +int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const +{ + 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); + + { + QString root = mappingRoot(); + if(!root.isEmpty()) { + if(root == path) { + path = QLatin1String("/"); + } else { + if(!root.endsWith(QLatin1String("/"))) + root += QLatin1String("/"); + if(path.size() >= root.size() && path.startsWith(root)) + path = path.mid(root.length()-1); + if(path.isEmpty()) + path = QLatin1String("/"); + } + } + } +#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; + QStringList segments = path.split(QLatin1Char('/'), QString::SkipEmptyParts); +#ifdef DEBUG_RESOURCE_MATCH + qDebug() << "****" << segments; +#endif + for(int i = 0; child_count && i < segments.size(); ++i) { + const QString &segment = segments[i]; +#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(i == segments.size()-1) { + 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) +#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; +} + +//file type handler +class QResourceFileEngineHandler : public QAbstractFileEngineHandler +{ +public: + QResourceFileEngineHandler() { } + ~QResourceFileEngineHandler() { } + QAbstractFileEngine *create(const QString &path) const; +}; +QAbstractFileEngine *QResourceFileEngineHandler::create(const QString &path) const +{ + if (path.size() > 0 && path.startsWith(QLatin1Char(':'))) + return new QResourceFileEngine(path); + return 0; +} + +//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 false; +} + +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 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 (!resource.isValid() + || offset < 0 + || size < 0 + || offset + size > resource.size() + || (size == 0)) { + 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; +} + +//Initialization and cleanup +Q_GLOBAL_STATIC(QResourceFileEngineHandler, resource_file_handler) + +static int qt_force_resource_init() { resource_file_handler(); return 1; } +Q_CORE_EXPORT void qInitResourceIO() { resource_file_handler(); } +static int qt_forced_resource_init = qt_force_resource_init(); +Q_CONSTRUCTOR_FUNCTION(qt_force_resource_init) + +QT_END_NAMESPACE diff --git a/src/corelib/io/qresource.h b/src/corelib/io/qresource.h new file mode 100644 index 0000000..1e9b2d5 --- /dev/null +++ b/src/corelib/io/qresource.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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: + 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 0000000..0e7c0dd --- /dev/null +++ b/src/corelib/io/qresource_iterator.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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. + QResourceFileEngineIterator *that = const_cast<QResourceFileEngineIterator *>(this); + that->entries = resource.children(); + if (!that->entries.isEmpty()) + that->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 0000000..0cf8fe7 --- /dev/null +++ b/src/corelib/io/qresource_iterator_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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: + QStringList entries; + 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 0000000..9706e93 --- /dev/null +++ b/src/corelib/io/qresource_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 0000000..62b4ed5 --- /dev/null +++ b/src/corelib/io/qsettings.cpp @@ -0,0 +1,3822 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 "qlibrary.h" + +#endif // Q_OS_WIN +#endif // QT_NO_QOBJECT + +#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 + +// ************************************************************************ +// 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 + +static bool isLikelyToBeNfs(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 + +static bool isLikelyToBeNfs(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_QNX6) || 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 + +static bool isLikelyToBeNfs(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 +static inline bool isLikelyToBeNfs(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 (isLikelyToBeNfs(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); +} + +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() && dir.isReadable()) { + return true; + } else { + 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; + 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 : Qt::CaseInsensitive; +#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 + QLibrary library(QLatin1String("shell32")); + QT_WA( { + typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPTSTR, int, BOOL); + GetSpecialFolderPath SHGetSpecialFolderPath = (GetSpecialFolderPath)library.resolve("SHGetSpecialFolderPathW"); + if (SHGetSpecialFolderPath) { + TCHAR path[MAX_PATH]; + SHGetSpecialFolderPath(0, path, type, FALSE); + result = QString::fromUtf16((ushort*)path); + } + } , { + typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, char*, int, BOOL); + GetSpecialFolderPath SHGetSpecialFolderPath = (GetSpecialFolderPath)library.resolve("SHGetSpecialFolderPathA"); + if (SHGetSpecialFolderPath) { + char path[MAX_PATH]; + SHGetSpecialFolderPath(0, path, type, FALSE); + result = QString::fromLocal8Bit(path); + } + } ); +#else + QLibrary library(QLatin1String("coredll")); + typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPTSTR, int, BOOL); + GetSpecialFolderPath SHGetSpecialFolderPath = (GetSpecialFolderPath)library.resolve("SHGetSpecialFolderPath"); + if (SHGetSpecialFolderPath) { + wchar_t path[MAX_PATH]; + SHGetSpecialFolderPath(0, path, type, FALSE); + result = QString::fromUtf16((ushort*)path); + } +#endif // Q_OS_WINCE + +#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 QString getPath(QSettings::Format format, QSettings::Scope scope) +{ + Q_ASSERT((int)QSettings::NativeFormat == 0); + Q_ASSERT((int)QSettings::IniFormat == 1); + + QString homePath = QDir::homePath(); + QString systemPath; + + globalMutex()->lock(); + PathHash *pathHash = pathHashFunc(); + bool loadSystemPath = pathHash->isEmpty(); + globalMutex()->unlock(); + + if (loadSystemPath) { + /* + 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('/'); + } + + QMutexLocker locker(globalMutex()); + 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('/'); +#ifdef Q_WS_QWS + 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 + } + + 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(); + + for (i = 0; i < NumConfFiles; ++i) + confFiles[i] = 0; + + 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] = QConfFile::fromName(userPath + appFile, true); + confFiles[F_User | F_Organization] = QConfFile::fromName(userPath + orgFile, true); + } + + QString systemPath = getPath(format, QSettings::SystemScope); + if (!application.isEmpty()) + confFiles[F_System | F_Application] = QConfFile::fromName(systemPath + appFile, false); + confFiles[F_System | F_Organization] = 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] = QConfFile::fromName(fileName, true); + for (int i = 1; i < NumConfFiles; ++i) + confFiles[i] = 0; + + 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 (usedHash) + usedHash->remove(confFiles[i]->name); + + if (confFiles[i]->size == 0) { + delete confFiles[i]; + } else if (unusedCache) { + // compute a better size? + unusedCache->insert(confFiles[i]->name, confFiles[i], + 10 + (confFiles[i]->originalKeys.size() / 4)); + } + } + } +} + +void QConfFileSettingsPrivate::remove(const QString &key) +{ + QConfFile *confFile = confFiles[spec]; + 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]; + 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]) { + 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]) { + 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]; + 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]; + if (confFile) { + QMutexLocker locker(&confFile->mutex); + syncConfFile(i); + } + } +} + +void QConfFileSettingsPrivate::flush() +{ + sync(); +} + +QString QConfFileSettingsPrivate::fileName() const +{ + QConfFile *confFile = confFiles[spec]; + if (!confFile) + return QString(); + return confFile->name; +} + +bool QConfFileSettingsPrivate::isWritable() const +{ + if (format > QSettings::IniFormat && !writeFunc) + return false; + + QConfFile *confFile = confFiles[spec]; + if (!confFile) + return false; + + return confFile->isWritable(); +} + +void QConfFileSettingsPrivate::syncConfFile(int confFileNo) +{ + QConfFile *confFile = confFiles[confFileNo]; + 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()); + + QT_WA( { + writeSemaphore = CreateSemaphoreW(0, 1, 1, reinterpret_cast<const wchar_t *>(writeSemName.utf16())); + } , { + writeSemaphore = CreateSemaphoreA(0, 1, 1, writeSemName.toLocal8Bit()); + } ); + + 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()); + + QT_WA( { + readSemaphore = CreateSemaphoreW(0, FileLockSemMax, FileLockSemMax, reinterpret_cast<const wchar_t *>(readSemName.utf16())); + } , { + readSemaphore = CreateSemaphoreA(0, FileLockSemMax, FileLockSemMax, readSemName.toLocal8Bit()); + } ); + + 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 + \ingroup misc + \mainclass + \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 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 two 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 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. Changes + performed by another process aren't visible in the current + process until sync() is called. + + \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 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 + + 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 QSettings::UserScope and the format is + QSettings::NativeFormat. + + \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 QSettings::NativeFormat. + + 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). + + 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) + d->flush(); +#ifdef QT_NO_QOBJECT + delete d; +#endif +} + +/*! + 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{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{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{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{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, used for storing + settings for the QSettings(QObject *) constructor. + + If no default format is set, QSettings::NativeFormat is used. + + \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(); + 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 + + \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 + + \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 constuctor. + 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) + delete oldPriv; + d_ptr = 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 0000000..cbb24ba --- /dev/null +++ b/src/corelib/io/qsettings.h @@ -0,0 +1,313 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 + 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 0000000..9a9e2d7 --- /dev/null +++ b/src/corelib/io/qsettings_mac.cpp @@ -0,0 +1,654 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 0000000..dd72fd9 --- /dev/null +++ b/src/corelib/io/qsettings_p.h @@ -0,0 +1,313 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 + +#ifdef Q_OS_WIN +#include "QtCore/qt_windows.h" +#endif + +QT_BEGIN_NAMESPACE + +#if defined(Q_WS_QWS) +#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_CORE_EXPORT QConfFile +{ +public: + 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; + + 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 0000000..a08c969 --- /dev/null +++ b/src/corelib/io/qsettings_win.cpp @@ -0,0 +1,962 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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) +{ + QString result; + QT_WA({ + wchar_t *data = 0; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + 0, errorCode, 0, + data, 0, 0); + result = QString::fromUtf16(reinterpret_cast<const ushort *> (data)); + if (data != 0) + LocalFree(data); + }, { + char *data = 0; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + 0, errorCode, 0, + (char *)&data, 0, 0); + result = QString::fromLocal8Bit(data); + if (data != 0) + LocalFree(data); + }) + if (result.endsWith(QLatin1String("\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; + QT_WA( { + res = RegOpenKeyExW(parentHandle, reinterpret_cast<const wchar_t *>(rSubKey.utf16()), + 0, perms, &resultHandle); + } , { + res = RegOpenKeyExA(parentHandle, rSubKey.toLocal8Bit(), + 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; + QT_WA( { + res = RegCreateKeyExW(parentHandle, reinterpret_cast<const wchar_t *>(rSubKey.utf16()), 0, 0, + REG_OPTION_NON_VOLATILE, perms, 0, &resultHandle, 0); + } , { + res = RegCreateKeyExA(parentHandle, rSubKey.toLocal8Bit(), 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; + LONG res; + DWORD numKeys; + DWORD maxKeySize; + DWORD numSubgroups; + DWORD maxSubgroupSize; + + // Find the number of keys and subgroups, as well as the max of their lengths. + QT_WA( { + res = RegQueryInfoKeyW(parentHandle, 0, 0, 0, &numSubgroups, &maxSubgroupSize, 0, + &numKeys, &maxKeySize, 0, 0, 0); + }, { + res = RegQueryInfoKeyA(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; + } + + /* Windows NT/2000/XP: The size does not include the terminating null character. + Windows Me/98/95: The size includes the terminating null character. */ + ++m; + + // Get the list + QByteArray buff(m*sizeof(ushort), 0); + for (int i = 0; i < n; ++i) { + QString item; + QT_WA( { + DWORD l = buff.size() / sizeof(ushort); + if (spec == QSettingsPrivate::ChildKeys) { + res = RegEnumValueW(parentHandle, i, + reinterpret_cast<wchar_t *>(buff.data()), + &l, 0, 0, 0, 0); + } else { + res = RegEnumKeyExW(parentHandle, i, + reinterpret_cast<wchar_t *>(buff.data()), + &l, 0, 0, 0, 0); + } + if (res == ERROR_SUCCESS) + item = QString::fromUtf16(reinterpret_cast<ushort*>(buff.data()), l); + }, { + DWORD l = buff.size(); + if (spec == QSettingsPrivate::ChildKeys) + res = RegEnumValueA(parentHandle, i, buff.data(), &l, 0, 0, 0, 0); + else + res = RegEnumKeyExA(parentHandle, i, buff.data(), &l, 0, 0, 0, 0); + if (res == ERROR_SUCCESS) + item = QString::fromLocal8Bit(buff.data(), 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; + QT_WA( { + res = RegDeleteKeyW(parentHandle, reinterpret_cast<const wchar_t *>(group.utf16())); + }, { + res = RegDeleteKeyA(parentHandle, group.toLocal8Bit()); + } ); + 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; + QT_WA( { + res = RegQueryValueExW(handle, reinterpret_cast<const wchar_t *>(rSubkeyName.utf16()), 0, &dataType, 0, &dataSize); + }, { + res = RegQueryValueExA(handle, rSubkeyName.toLocal8Bit(), 0, &dataType, 0, &dataSize); + } ); + if (res != ERROR_SUCCESS) { + RegCloseKey(handle); + return false; + } + + // get the value + QByteArray data(dataSize, 0); + QT_WA( { + res = RegQueryValueExW(handle, reinterpret_cast<const wchar_t *>(rSubkeyName.utf16()), 0, 0, + reinterpret_cast<unsigned char*>(data.data()), &dataSize); + }, { + res = RegQueryValueExA(handle, rSubkeyName.toLocal8Bit(), 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) { + QT_WA( { + s = QString::fromUtf16(((const ushort*)data.constData())); + }, { + s = QString::fromLocal8Bit(data.constData()); + } ); + } + if (value != 0) + *value = stringToVariant(s); + break; + } + + case REG_MULTI_SZ: { + QStringList l; + if (dataSize) { + int i = 0; + for (;;) { + QString s; + QT_WA( { + s = QString::fromUtf16((const ushort*)data.constData() + i); + }, { + s = QString::fromLocal8Bit(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) { + QT_WA( { + s = QString::fromUtf16((const ushort*)data.constData(), data.size()/2); + }, { + s = QString::fromLocal8Bit(data.constData(), data.size()); + } ); + } + 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; + } + + 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 + DWORD res; + QString emptyKey; + QT_WA( { + res = RegDeleteKeyW(writeHandle(), reinterpret_cast<const wchar_t *>(emptyKey.utf16())); + }, { + res = RegDeleteKeyA(writeHandle(), emptyKey.toLocal8Bit()); + } ); + 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) { + QT_WA( { + res = RegDeleteValueW(handle, reinterpret_cast<const wchar_t *>(keyName(rKey).utf16())); + }, { + res = RegDeleteValueA(handle, keyName(rKey).toLocal8Bit()); + } ); + 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; + QT_WA( { + res = RegDeleteValueW(handle, reinterpret_cast<const wchar_t *>(group.utf16())); + }, { + res = RegDeleteValueA(handle, group.toLocal8Bit()); + } ); + 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 + QT_WA( { + res = RegDeleteKeyW(writeHandle(), reinterpret_cast<const wchar_t *>(rKey.utf16())); + }, { + res = RegDeleteKeyA(writeHandle(), rKey.toLocal8Bit()); + } ); + + 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); + QT_WA( { + regValueBuff = QByteArray((const char*)s.utf16(), s.length()*2); + }, { + regValueBuff = QByteArray((const char*)s.toLocal8Bit(), s.length()); + } ); + } else { + QStringList::const_iterator it = l.constBegin(); + for (; it != l.constEnd(); ++it) { + const QString &s = *it; + QT_WA( { + regValueBuff += QByteArray((const char*)s.utf16(), (s.length() + 1)*2); + }, { + regValueBuff += QByteArray((const char*)s.toLocal8Bit(), s.length() + 1); + } ); + } + QT_WA( { + regValueBuff.append((char)0); + regValueBuff.append((char)0); + }, { + regValueBuff.append((char)0); + } ); + } + break; + } + + case QVariant::Int: { + type = REG_DWORD; + int i = value.toInt(); + regValueBuff = QByteArray((const char*)&i, sizeof(int)); + break; + } + + case QVariant::ByteArray: + // On Win95/98/Me QString::toLocal8Bit() fails to handle chars > 0x7F. So we don't go through variantToString() at all. + if (QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based) { + QByteArray ba = value.toByteArray(); + regValueBuff = "@ByteArray("; + regValueBuff += ba; + regValueBuff += ')'; + if (ba.contains('\0')) { + type = REG_BINARY; + } else { + type = REG_SZ; + regValueBuff += '\0'; + } + + break; + } + // 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) { + QT_WA( { + regValueBuff = QByteArray((const char*)s.utf16(), s.length()*2); + }, { + regValueBuff = QByteArray((const char*)s.toLocal8Bit(), s.length()); + } ); + } else { + QT_WA( { + regValueBuff = QByteArray((const char*)s.utf16(), (s.length() + 1)*2); + }, { + regValueBuff = QByteArray((const char*)s.toLocal8Bit(), s.length() + 1); + } ); + } + break; + } + } + + // set the value + LONG res; + QT_WA( { + res = RegSetValueExW(handle, reinterpret_cast<const wchar_t *>(keyName(rKey).utf16()), 0, type, + reinterpret_cast<const unsigned char*>(regValueBuff.constData()), + regValueBuff.size()); + }, { + res = RegSetValueExA(handle, keyName(rKey).toLocal8Bit(), 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 0000000..d67d2ea --- /dev/null +++ b/src/corelib/io/qtemporaryfile.cpp @@ -0,0 +1,710 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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" + +#include <stdlib.h> +#if !defined(Q_OS_WINCE) +# include <errno.h> +# include <sys/stat.h> +# include <sys/types.h> +#endif + +#include <stdlib.h> +#include <time.h> +#include <ctype.h> + +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) +# include <process.h> +# if defined(_MSC_VER) && _MSC_VER >= 1400 +# include <share.h> +# endif +#endif + +#if defined(Q_OS_WINCE) +# include <types.h> +# include "qfunctions_wince.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. + */ +static int _gettemp(char *path, int *doopen, int domkdir, int slen) +{ + char *start, *trv, *suffp; + QT_STATBUF sbuf; + int rval; +#if defined(Q_OS_WIN) + int pid; +#else + pid_t pid; +#endif + + if (doopen && domkdir) { + errno = EINVAL; + return(0); + } + + for (trv = path; *trv; ++trv) + ; + trv -= slen; + suffp = trv; + --trv; + if (trv < path) { + errno = EINVAL; + return (0); + } +#if defined(Q_OS_WIN) && defined(_MSC_VER) && _MSC_VER >= 1400 + pid = _getpid(); +#else + pid = getpid(); +#endif + while (trv >= path && *trv == 'X' && pid != 0) { + *trv-- = (pid % 10) + '0'; + pid /= 10; + } + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif + + while (trv >= path && *trv == 'X') { + char c; + + // CHANGE arc4random() -> random() + pid = (qrand() & 0xffff) % (26+26); + if (pid < 26) + c = pid + 'A'; + else + c = (pid - 26) + 'a'; + *trv-- = c; + } + start = trv + 1; + + /* + * check the target directory; if you have six X's and it + * doesn't exist this runs for a *very* long time. + */ + if (doopen || domkdir) { + for (;; --trv) { + if (trv <= path) + break; + if (*trv == '/') { + *trv = '\0'; +#if defined (Q_OS_WIN) && !defined(Q_OS_WINCE) + if (trv - path == 2 && path[1] == ':') { + // Special case for Windows drives + // (e.g., "C:" => "C:\"). + // ### Better to use a Windows + // call for this. + char drive[] = "c:\\"; + drive[0] = path[0]; + rval = QT_STAT(drive, &sbuf); + } else +#endif + rval = QT_STAT(path, &sbuf); + *trv = '/'; + if (rval != 0) + return(0); + if (!S_ISDIR(sbuf.st_mode)) { + errno = ENOTDIR; + return(0); + } + break; + } + } + } + + for (;;) { + if (doopen) { +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) && defined(_MSC_VER) && _MSC_VER >= 1400 + if (_sopen_s(doopen, path, QT_OPEN_CREAT|O_EXCL|QT_OPEN_RDWR|QT_OPEN_BINARY +#ifdef QT_LARGEFILE_SUPPORT + |QT_OPEN_LARGEFILE +#endif + , _SH_DENYNO, _S_IREAD | _S_IWRITE)== 0) +#else +#if defined(Q_OS_WINCE) + QString targetPath; + if (QDir::isAbsolutePath(QString::fromLatin1(path))) + targetPath = QLatin1String(path); + else + targetPath = QDir::currentPath().append(QLatin1String("/")) + QLatin1String(path); + + if ((*doopen = + QT_OPEN(targetPath.toLocal8Bit(), O_CREAT|O_EXCL|O_RDWR +#else + if ((*doopen = + open(path, QT_OPEN_CREAT|O_EXCL|QT_OPEN_RDWR +#endif +#ifdef QT_LARGEFILE_SUPPORT + |QT_OPEN_LARGEFILE +#endif +# if defined(Q_OS_WINCE) + |_O_BINARY +# elif defined(Q_OS_WIN) + |O_BINARY +# endif + , 0600)) >= 0) +#endif + + return(1); + if (errno != EEXIST) + return(0); + } else if (domkdir) { +#ifdef Q_OS_WIN + if (QT_MKDIR(path) == 0) +#else + if (mkdir(path, 0700) == 0) +#endif + return(1); + if (errno != EEXIST) + return(0); + } +#ifndef Q_OS_WIN + else if (QT_LSTAT(path, &sbuf)) + return(errno == ENOENT ? 1 : 0); +#else + if (!QFileInfo(QLatin1String(path)).exists()) + return 1; +#endif + + /* tricky little algorwwithm for backward compatibility */ + for (trv = start;;) { + if (!*trv) + return (0); + if (*trv == 'Z') { + if (trv == suffp) + return (0); + *trv++ = 'a'; + } else { + if (isdigit(*trv)) + *trv = 'a'; + else if (*trv == 'z') /* inc from z to A */ + *trv = 'A'; + else { + if (trv == suffp) + return (0); + ++*trv; + } + break; + } + } + } + /*NOTREACHED*/ +} + +static int qt_mkstemps(char *path, int slen) +{ + int fd = 0; + return (_gettemp(path, &fd, 0, slen) ? fd : -1); +} + +//************* QTemporaryFileEngine +class QTemporaryFileEngine : public QFSFileEngine +{ + Q_DECLARE_PRIVATE(QFSFileEngine) +public: + QTemporaryFileEngine(const QString &file) : QFSFileEngine(file) { } + ~QTemporaryFileEngine(); + + bool open(QIODevice::OpenMode flags); + bool remove(); + bool close(); +}; + +QTemporaryFileEngine::~QTemporaryFileEngine() +{ + QFSFileEngine::close(); +} + +bool QTemporaryFileEngine::open(QIODevice::OpenMode openMode) +{ + Q_D(QFSFileEngine); + + QString qfilename = d->filePath; + if(!qfilename.contains(QLatin1String("XXXXXX"))) + qfilename += QLatin1String(".XXXXXX"); + + int suffixLength = qfilename.length() - (qfilename.lastIndexOf(QLatin1String("XXXXXX"), -1, Qt::CaseSensitive) + 6); + d->closeFileHandle = true; + char *filename = qstrdup(qfilename.toLocal8Bit()); + +#ifndef Q_WS_WIN + int fd = qt_mkstemps(filename, suffixLength); + if (fd != -1) { + // First open the fd as an external file descriptor to + // initialize the engine properly. + 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->filePath = QString::fromLocal8Bit(filename); //changed now! + d->nativeInitFileName(); + delete [] filename; + return true; + } + delete [] filename; + setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, qt_error_string(errno)); + return false; +#else + if (!_gettemp(filename, 0, 0, suffixLength)) { + delete [] filename; + return false; + } + + d->filePath = QString::fromLocal8Bit(filename); + d->nativeInitFileName(); + d->closeFileHandle = true; + delete [] filename; + return QFSFileEngine::open(openMode); +#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(); + bool removed = QFSFileEngine::remove(); + d->filePath.clear(); + return removed; +} + +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; + mutable QTemporaryFileEngine *fileEngine; +}; + +QTemporaryFilePrivate::QTemporaryFilePrivate() : autoRemove(true), fileEngine(0) +{ +} + +QTemporaryFilePrivate::~QTemporaryFilePrivate() +{ + delete fileEngine; + fileEngine = 0; +} + +//************* QTemporaryFile + +/*! + \class QTemporaryFile + \reentrant + \brief The QTemporaryFile class is an I/O device that operates on temporary files. + + \ingroup io + \mainclass + + 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 while the file is open; the function returns + an empty string before the file is opened and after it is closed. + + 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"); +#ifdef Q_OS_SYMBIAN + //Just for verify that folder really exist on hardware + fileEngine()->mkdir( QDir::tempPath(), true ); +#endif +} + +/*! + 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) +{ + setFileTemplate(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) +{ + setFileTemplate(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 +{ + if(!isOpen()) + 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_ASSERT(!isOpen()); + Q_D(QTemporaryFile); + fileEngine()->setFileName(name); + d->templateName = 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) + d->fileEngine = new QTemporaryFileEngine(d->templateName); + 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()) { + setOpenMode(flags); + return true; + } + + if (QFile::open(flags)) { + d->fileName = d->fileEngine->fileName(QAbstractFileEngine::DefaultName); + return true; + } + return false; +} + +#endif // QT_NO_TEMPORARYFILE + +QT_END_NAMESPACE diff --git a/src/corelib/io/qtemporaryfile.h b/src/corelib/io/qtemporaryfile.h new file mode 100644 index 0000000..4c3eb9a --- /dev/null +++ b/src/corelib/io/qtemporaryfile.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 0000000..512332e --- /dev/null +++ b/src/corelib/io/qtextstream.cpp @@ -0,0 +1,3387 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 text + \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. + + 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. + + \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 <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 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, + EndOfFile + }; + + 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 write(const QString &data); + inline bool getChar(QChar *ch); + inline void ungetChar(const QChar &ch); + NumberParsingStatus getNumber(qulonglong *l); + bool getReal(double *f); + + bool putNumber(qulonglong number, bool negative); + inline bool putString(const QString &ch, bool number = false); + + // buffers + bool fillReadBuffer(qint64 maxBytes = -1); + void resetReadBuffer(); + bool flushWriteBuffer(); + QString writeBuffer; + QString readBuffer; + int readBufferOffset; + 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 + 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; + + if (bytesRead >= 4 && ((uchar(buf[0]) == 0xff && uchar(buf[1]) == 0xfe && uchar(buf[2]) == 0 && uchar(buf[3]) == 0) + || (uchar(buf[0]) == 0 && uchar(buf[1]) == 0 && uchar(buf[2]) == 0xfe && uchar(buf[3]) == 0xff))) { + codec = QTextCodec::codecForName("UTF-32"); + } else if (bytesRead >= 2 && ((uchar(buf[0]) == 0xff && uchar(buf[1]) == 0xfe) + || (uchar(buf[0]) == 0xfe && uchar(buf[1]) == 0xff))) { + codec = QTextCodec::codecForName("UTF-16"); + } else 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 +*/ +bool QTextStreamPrivate::flushWriteBuffer() +{ + // no buffer next to the QString itself; this function should only + // be called internally, for devices. + if (string || !device) + return false; + if (writeBuffer.isEmpty()) + return true; + +#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) + return false; + +#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 + return flushed && bytesWritten == qint64(data.size()); +} + +/*! \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; + + if (delimiter == Space && ch.isSpace()) { + foundToken = true; + delimSize = 1; + } else if (delimiter == NotSpace && !ch.isSpace()) { + foundToken = true; + delimSize = 1; + } else if (delimiter == EndOfLine && ch == QLatin1Char('\n')) { + foundToken = true; + delimSize = (lastChar == QLatin1Char('\r')) ? 2 : 1; + consumeDelimiter = true; + } + + lastChar = ch; + } + } 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 (totalSize > 0 && !foundToken && delimiter == EndOfLine) { + 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()); + } + } +} + +/*! \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; +} + +/*! \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 bool QTextStreamPrivate::write(const QString &data) +{ + if (string) { + // ### What about seek()?? + string->append(data); + } else { + writeBuffer += data; + if (writeBuffer.size() > QTEXTSTREAM_BUFFERSIZE) + return flushWriteBuffer(); + } + return true; +} + +/*! \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 bool QTextStreamPrivate::putString(const QString &s, bool number) +{ + QString tmp = s; + + // handle padding + int padSize = fieldWidth - s.size(); + if (padSize > 0) { + QString pad(padSize > 0 ? padSize : 0, 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 + return 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(); + + delete d; + d_ptr = 0; +} + +/*! + 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; +#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; + while (d->readBuffer.size() < oldReadBufferOffset) { + if (!thatd->fillReadBuffer(1)) + return qint64(-1); + } + thatd->readBufferOffset = oldReadBufferOffset; + + // 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 \a 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. + + \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()); + + const QChar *readPtr; + int length; + if (!d->scan(&readPtr, &length, /* maxlen = */ 0, QTextStreamPrivate::EndOfFile)) + return QString(); + + QString tmp = QString(readPtr, length); + d->consumeLastToken(); + return tmp; +} + +/*! + 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 + + const QChar *readPtr; + int length; + if (!d->scan(&readPtr, &length, int(maxlen), QTextStreamPrivate::EndOfFile)) + return QString(); + + QString tmp = QString(readPtr, length); + d->consumeLastToken(); + return tmp; +} + +/*! \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.language() != 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.language() != 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 implmentation 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 + */ +bool 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.language() != 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(QLatin1String("0")); + } + } + return 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. + + \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. + + \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 + \ingroup io + \ingroup text + + 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 + \ingroup io + \ingroup text + + 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 0000000..6302d34 --- /dev/null +++ b/src/corelib/io/qtextstream.h @@ -0,0 +1,375 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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> + +#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 + }; + 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) + + 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; } + 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 0000000..9ce9a2e --- /dev/null +++ b/src/corelib/io/qurl.cpp @@ -0,0 +1,5999 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QUrl + + \brief The QUrl class provides a convenient interface for working + with URLs. + + \reentrant + \ingroup io + \ingroup misc + \ingroup shared + \mainclass + + 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. +*/ + +#include "qplatformdefs.h" +#include "qurl.h" +#include "private/qunicodetables_p.h" +#include "qatomic.h" +#include "qbytearray.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; + + char valueDelimiter; + char pairDelimiter; + + enum State { + Parsed = 0x1, + Validated = 0x2, + Normalized = 0x4, + HostCanonicalized = 0x8 + }; + int stateFlags; + + QByteArray encodedNormalized; + const QByteArray & normalized(); + + 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 void QT_FASTCALL _scheme(const char **ptr, QUrlParseData *parseData) +{ + bool first = true; + + parseData->scheme = *ptr; + for (;;) { + char ch = **ptr; + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + ; + } else if (!first && ((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.')) { + ; + } else { + break; + } + + ++(*ptr); + first = false; + } + + if (**ptr != ':') { + *ptr = parseData->scheme; + } else { + parseData->schemeLength = *ptr - parseData->scheme; + ++(*ptr); // skip ':' + } +} + +// 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 { + int uc; + ushort mapping[4]; +}; + +inline bool operator<(int one, const NameprepCaseFoldingEntry &other) +{ return one < other.uc; } + +inline bool operator<(const NameprepCaseFoldingEntry &one, int 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, { 0x10428, 0x0000, 0x0000, 0x0000 } }, + { 0x10401, { 0x10429, 0x0000, 0x0000, 0x0000 } }, + { 0x10402, { 0x1042A, 0x0000, 0x0000, 0x0000 } }, + { 0x10403, { 0x1042B, 0x0000, 0x0000, 0x0000 } }, + { 0x10404, { 0x1042C, 0x0000, 0x0000, 0x0000 } }, + { 0x10405, { 0x1042D, 0x0000, 0x0000, 0x0000 } }, + { 0x10406, { 0x1042E, 0x0000, 0x0000, 0x0000 } }, + { 0x10407, { 0x1042F, 0x0000, 0x0000, 0x0000 } }, + { 0x10408, { 0x10430, 0x0000, 0x0000, 0x0000 } }, + { 0x10409, { 0x10431, 0x0000, 0x0000, 0x0000 } }, + { 0x1040A, { 0x10432, 0x0000, 0x0000, 0x0000 } }, + { 0x1040B, { 0x10433, 0x0000, 0x0000, 0x0000 } }, + { 0x1040C, { 0x10434, 0x0000, 0x0000, 0x0000 } }, + { 0x1040D, { 0x10435, 0x0000, 0x0000, 0x0000 } }, + { 0x1040E, { 0x10436, 0x0000, 0x0000, 0x0000 } }, + { 0x1040F, { 0x10437, 0x0000, 0x0000, 0x0000 } }, + { 0x10410, { 0x10438, 0x0000, 0x0000, 0x0000 } }, + { 0x10411, { 0x10439, 0x0000, 0x0000, 0x0000 } }, + { 0x10412, { 0x1043A, 0x0000, 0x0000, 0x0000 } }, + { 0x10413, { 0x1043B, 0x0000, 0x0000, 0x0000 } }, + { 0x10414, { 0x1043C, 0x0000, 0x0000, 0x0000 } }, + { 0x10415, { 0x1043D, 0x0000, 0x0000, 0x0000 } }, + { 0x10416, { 0x1043E, 0x0000, 0x0000, 0x0000 } }, + { 0x10417, { 0x1043F, 0x0000, 0x0000, 0x0000 } }, + { 0x10418, { 0x10440, 0x0000, 0x0000, 0x0000 } }, + { 0x10419, { 0x10441, 0x0000, 0x0000, 0x0000 } }, + { 0x1041A, { 0x10442, 0x0000, 0x0000, 0x0000 } }, + { 0x1041B, { 0x10443, 0x0000, 0x0000, 0x0000 } }, + { 0x1041C, { 0x10444, 0x0000, 0x0000, 0x0000 } }, + { 0x1041D, { 0x10445, 0x0000, 0x0000, 0x0000 } }, + { 0x1041E, { 0x10446, 0x0000, 0x0000, 0x0000 } }, + { 0x1041F, { 0x10447, 0x0000, 0x0000, 0x0000 } }, + { 0x10420, { 0x10448, 0x0000, 0x0000, 0x0000 } }, + { 0x10421, { 0x10449, 0x0000, 0x0000, 0x0000 } }, + { 0x10422, { 0x1044A, 0x0000, 0x0000, 0x0000 } }, + { 0x10423, { 0x1044B, 0x0000, 0x0000, 0x0000 } }, + { 0x10424, { 0x1044C, 0x0000, 0x0000, 0x0000 } }, + { 0x10425, { 0x1044D, 0x0000, 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 N = sizeof(NameprepCaseFolding) / sizeof(NameprepCaseFolding[0]); + + QChar *d = 0; + for (int i = 0; i < str->size(); ++i) { + int uc = str->at(i).unicode(); + if (uc < 0x80) { + if (uc <= 'Z' && uc >= 'A') { + uc |= 0x20; + if (!d) + d = str->data(); + d[i] = QChar(uc); + } + } else { + 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) { + str->replace(i, 1, (const QChar *)&entry->mapping[0], l); + d = 0; + } else { + if (!d) + d = str->data(); + d[i] = QChar(entry->mapping[0]); + } + } + } + } +} + +static bool isMappedToNothing(const QChar &ch) +{ + if (ch.unicode() < 0xad) + return false; + switch (ch.unicode()) { + 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) +{ + ushort *out = (ushort *)str->data(); + const ushort *in = out; + const ushort *end = out + str->size(); + while (in < end) { + ushort uc = *in; + if (uc < 0x80 || + !(uc <= 0x009F + || uc == 0x00A0 + || uc == 0x0340 + || uc == 0x0341 + || uc == 0x06DD + || uc == 0x070F + || uc == 0x1680 + || uc == 0x180E + || (uc >= 0x2000 && uc <= 0x200B) + || uc == 0x200C + || uc == 0x200D + || uc == 0x200E + || uc == 0x200F + || (uc >= 0x2028 && uc <= 0x202F) + || uc == 0x205F + || (uc >= 0x2060 && uc <= 0x2063) + || uc == 0x206A + || (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 <= 0xFFFC) + || (uc >= 0xFFFA && (uc <= 0xFFFE || uc == 0xFFFF)) + /* ### Add NAMEPREP support for surrogates + || uc == 0xE0001 + || (uc >= 0x2FFFE && uc <= 0x2FFFF) + || (uc >= 0x1D173 && uc <= 0x1D17A) + || (uc >= 0x1FFFE && uc <= 0x1FFFF) + || (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 >= 0xE0020 && uc <= 0xE007F) + || (uc >= 0xEFFFE && uc <= 0xEFFFF) + || (uc >= 0xF0000 && uc <= 0xFFFFD) + || (uc >= 0xFFFFE && uc <= 0xFFFFF) + || (uc >= 0x100000 && uc <= 0x10FFFD) + || (uc >= 0x10FFFE && uc <= 0x10FFFF)*/)) + *out++ = *in; + ++in; + } + if (in != out) + str->truncate(out - str->utf16()); +} + +static bool isBidirectionalRorAL(const QChar &c) +{ + ushort uc = c.unicode(); + 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(const QChar &ch) +{ + ushort uc = ch.unicode(); + 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; + } + + /* ### Add NAMEPREP support for surrogates + || (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 false; +} + + +Q_AUTOTEST_EXPORT QString qt_nameprep(const QString &source) +{ + QString mapped = source; + + bool simple = true; + for (int i = 0; i < mapped.size(); ++i) { + ushort uc = mapped.at(i).unicode(); + if (uc > 0x80) { + simple = false; + break; + } else if (uc >= 'A' && uc <= 'Z') { + mapped[i] = QChar(uc | 0x20); + } + } + if (simple) + return mapped; + + // Characters commonly mapped to nothing are simply removed + // (Table B.1) + QChar *out = mapped.data(); + const QChar *in = out; + const QChar *e = in + mapped.size(); + while (in < e) { + if (!isMappedToNothing(*in)) + *out++ = *in; + ++in; + } + if (out != in) + mapped.truncate(out - mapped.constData()); + + // Map to lowercase (Table B.2) + mapToLowerCase(&mapped); + + // Normalize to Unicode 3.2 form KC + mapped = mapped.normalized(QString::NormalizationForm_KC, QChar::Unicode_3_2); + + // Strip prohibited output + stripProhibitedOutput(&mapped); + + // Check for valid bidirectional characters + bool containsLCat = false; + bool containsRandALCat = false; + for (int j = 0; j < mapped.size() && (!containsLCat || !containsRandALCat); ++j) { + if (isBidirectionalL(mapped.at(j))) + containsLCat = true; + else if (isBidirectionalRorAL(mapped.at(j))) + containsRandALCat = true; + } + if (containsRandALCat) { + if (containsLCat || (!isBidirectionalRorAL(mapped.at(0)) + || !isBidirectionalRorAL(mapped.at(mapped.size() - 1)))) + mapped.clear(); + } + + return mapped; +} + + +static inline char 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(QByteArray* 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 += encodeDigit(t + (qq - t) % (base - t)); + qq = (qq - t) / (base - t); + } + + *output += encodeDigit(qq); + bias = adapt(delta, h + 1, h == b); + delta = 0; + ++h; +} + +static void toPunycodeHelper(const QChar *s, int ucLength, QByteArray *output) +{ + uint n = initial_n; + uint delta = 0; + uint bias = initial_bias; + + int outLen = output->length(); + output->resize(outLen + ucLength); + + char *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 += 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, "xn--"); + return; +} + + +static const char * const idn_whitelist[] = { + "ac", "at", + "br", + "cat", "ch", "cl", "cn", + "de", "dk", + "fi", + "gr", + "hu", + "info", "io", "is", + "jp", + "kr", + "li", "lt", + "museum", + "no", + "org", + "se", "sh", + "th", "tm", "tw", + "vn", +}; + +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; + const QChar *tld = domain.constData() + idx + 1; + int len = domain.size() - idx - 1; + + if (user_idn_whitelist) + return user_idn_whitelist->contains(QString(tld, len)); + + 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 QString qt_from_ACE(const QString &domainMC) +{ + QString domain = domainMC.toLower(); + int idx = domain.indexOf(QLatin1Char('.')); + if (idx != -1) { + if (!domain.contains(QLatin1String("xn--"))) { + bool simple = true; + for (int i = 0; i < domain.size(); ++i) { + ushort ch = domain.at(i).unicode(); + if (ch > 'z' || ch < '-' || ch == '/' || (ch > '9' && ch < 'A') || (ch > 'Z' && ch < 'a')) { + simple = false; + break; + } + } + if (simple) + return domain; + } + + const bool isIdnEnabled = qt_is_idn_enabled(domain); + int lastIdx = 0; + QString result; + while (1) { + // Nameprep the host. If the labels in the hostname are Punycode + // encoded, we decode them immediately, then nameprep them. + QByteArray label; + toPunycodeHelper(domain.constData() + lastIdx, idx - lastIdx, &label); + result += qt_nameprep(isIdnEnabled ? QUrl::fromPunycode(label) : QString::fromLatin1(label)); + lastIdx = idx + 1; + if (lastIdx < domain.size() + 1) + result += QLatin1Char('.'); + else + break; + idx = domain.indexOf(QLatin1Char('.'), lastIdx); + if (idx == -1) + idx = domain.size(); + } + return result; + } else { + return qt_nameprep(domain); + } +} + + +QUrlPrivate::QUrlPrivate() +{ + ref = 1; + port = -1; + isValid = false; + 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), + valueDelimiter(copy.valueDelimiter), + pairDelimiter(copy.pairDelimiter), + stateFlags(copy.stateFlags), + encodedNormalized(copy.encodedNormalized) +{ ref = 1; } + +QString QUrlPrivate::canonicalHost() const +{ + if (QURL_HASFLAG(stateFlags, HostCanonicalized)) + return host; + + QUrlPrivate *that = const_cast<QUrlPrivate *>(this); + QURL_SETFLAG(that->stateFlags, HostCanonicalized); + that->host = qt_from_ACE(host); + 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) +{ + if (auth.isEmpty()) + 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 + + 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 + _scheme(ptr, &parseData); + + // 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(); + + QByteArray url; + + if (!(options & QUrl::RemoveScheme) && !scheme.isEmpty()) { + url += scheme.toLatin1(); + url += ':'; + } + QString auth = authority(); + bool doFileScheme = scheme == QLatin1String("file") && encodedPath.startsWith('/'); + if ((options & QUrl::RemoveAuthority) != QUrl::RemoveAuthority && (!auth.isEmpty() || doFileScheme)) { + if (doFileScheme && !encodedPath.startsWith('/')) + url += '/'; + url += "//"; + + if ((options & QUrl::RemoveUserInfo) != QUrl::RemoveUserInfo) { + if (!userName.isEmpty()) { + url += encodedUserName; + if (!(options & QUrl::RemovePassword) && !password.isEmpty()) { + url += ':'; + url += encodedPassword; + } + url += '@'; + } + } + + 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() +{ + if (QURL_HASFLAG(stateFlags, QUrlPrivate::Normalized)) + return encodedNormalized; + + QURL_SETFLAG(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) + 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, ": ")); + errorString += QLatin1String(errorInfo._message); + } + if (errorInfo._found) { + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ", but found \'")); + errorString += QLatin1Char(errorInfo._found); + errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, "\'")); + } + return errorString; +} + +/*! + 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(new QUrlPrivate) +{ + 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(new QUrlPrivate) +{ + if (!url.isEmpty()) + setUrl(url, parsingMode); + else + d->parsingMode = parsingMode; +} + +/*! + Constructs an empty QUrl object. +*/ +QUrl::QUrl() : d(new QUrlPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QUrl::QUrl(const QUrl &other) : d(other.d) +{ + d->ref.ref(); +} + +/*! + Destructor; called immediately before the object is deleted. +*/ +QUrl::~QUrl() +{ + if (!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 (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Validated)) d->validate(); + + return d->isValid; +} + +/*! + Returns true if the URL has no data; otherwise returns false. +*/ +bool QUrl::isEmpty() const +{ + 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() +{ + detach(); + d->clear(); +} + +/*! + 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) +{ + // 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) +{ + clear(); + QByteArray tmp = encodedUrl; + 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 (!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 (!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 (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized); + + 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 (!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 (!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 (!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 (!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 (!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 (!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 (!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 (!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 (!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 (!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 (!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 (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + detach(); + QURL_UNSETFLAG(d->stateFlags, QUrlPrivate::Validated | QUrlPrivate::Normalized | QUrlPrivate::HostCanonicalized); + + d->host = host; + if (d->host.contains(QLatin1Char(':'))) + d->host = QLatin1Char('[') + d->host + QLatin1Char(']'); +} + +/*! + Returns the host of the URL if it is defined; otherwise + an empty string is returned. +*/ +QString QUrl::host() const +{ + 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 (!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 (!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 (!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 (!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 (!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 (!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 (!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 (!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) +{ + 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 +{ + return d->pairDelimiter; +} + +/*! + Returns the character used to delimit between keys and values in + the query string of the URL. +*/ +char QUrl::queryValueDelimiter() const +{ + 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 (!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(). + + \sa setQueryDelimiters(), queryItems(), setEncodedQueryItems() +*/ +void QUrl::setQueryItems(const QList<QPair<QString, QString> > &query) +{ + 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 (!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. + + \sa addEncodedQueryItem() +*/ +void QUrl::addQueryItem(const QString &key, const QString &value) +{ + 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 (!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. + + \sa setQueryItems(), setEncodedQuery() +*/ +QList<QPair<QString, QString> > QUrl::queryItems() const +{ + 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 (!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 +{ + 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 (!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. + + \sa allQueryItemValues() +*/ +QString QUrl::queryItemValue(const QString &key) const +{ + 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 (!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. + + \sa queryItemValue() +*/ +QStringList QUrl::allQueryItemValues(const QString &key) const +{ + 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 (!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) +{ + 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 (!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) +{ + 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 (!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 (!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 (!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 (!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 (!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 (!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 (!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 (!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 { + 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(); + + 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 (!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 (!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.isEmpty() || 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.right(1) == QLatin1String("/")) + 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 +{ + 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) +{ + QByteArray output; + toPunycodeHelper(uc.constData(), uc.size(), &output); + return output; +} + +/*! + \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 "qtsoftware.com") to be written using international + characters. +*/ +QString QUrl::fromAce(const QByteArray &domain) +{ + return qt_from_ACE(QString::fromLatin1(domain)); +} + +/*! + \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 "qtsoftware.com") to be written using international + characters. +*/ +QByteArray QUrl::toAce(const QString &domain) +{ + // IDNA / rfc3490 describes these four delimiters used for + // separating labels in unicode international domain + // names. + QString nameprepped = qt_nameprep(domain); + int lastIdx = 0; + QByteArray result; + for (int i = 0; i < nameprepped.size(); ++i) { + ushort uc = nameprepped.at(i).unicode(); + if (uc == 0x2e || uc == 0x3002 || uc == 0xff0e || uc == 0xff61) { + if (lastIdx) + result += '.'; + toPunycodeHelper(nameprepped.constData() + lastIdx, i - lastIdx, &result); + lastIdx = i + 1; + } + } + if (lastIdx) + result += '.'; + toPunycodeHelper(nameprepped.constData() + lastIdx, nameprepped.size() - lastIdx, &result); + + return result; +} + +/*! + \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 (!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 equal; + otherwise returns false. +*/ +bool QUrl::operator ==(const QUrl &url) const +{ + 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) +{ + qAtomicAssign(d, url.d); + return *this; +} + +/*! + Assigns the specified \a url to this object. +*/ +QUrl &QUrl::operator =(const QString &url) +{ + QUrl tmp(url); + qAtomicAssign(d, tmp.d); + return *this; +} + +/*! \internal + + Forces a detach. +*/ +void QUrl::detach() +{ qAtomicDetach(d); } + +/*! + \internal +*/ +bool QUrl::isDetached() const +{ + return d->ref == 1; +} + + +/*! + Returns a QUrl representation of \a localFile, interpreted as a + local file. + + \sa toLocalFile() +*/ +QUrl QUrl::fromLocalFile(const QString &localFile) +{ + QUrl url; + url.setScheme(QLatin1String("file")); + QString deslashified = localFile; + deslashified.replace(QLatin1Char('\\'), QLatin1Char('/')); + + + + // magic for drives on windows + if (deslashified.length() > 1 && deslashified.at(1) == QLatin1Char(':') && deslashified.at(0) != QLatin1Char('/')) { + url.setPath(QLatin1String("/") + 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. + + \sa fromLocalFile() +*/ +QString QUrl::toLocalFile() const +{ + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + QString tmp; + QString ourPath = path(); + if (d->scheme.isEmpty() || d->scheme.toLower() == QLatin1String("file")) { + + // magic for shared drive on windows + if (!d->host.isEmpty()) { + tmp = QLatin1String("//") + d->host + (ourPath.length() > 0 && ourPath.at(0) != QLatin1Char('/') + ? QLatin1String("/") + 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; +} + +/*! + 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 +{ + if (!QURL_HASFLAG(d->stateFlags, QUrlPrivate::Parsed)) d->parse(); + + QString childPath = childUrl.path(); + 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://qtsoftware.com/Developer/"); + url.cdUp(); + \newcode + QUrl url("http://qtsoftware.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 +{ + return d->createErrorString(); +} + +/*! + \typedef QUrl::DataPtr + \internal +*/ + +/*! + \fn DataPtr &QUrl::data_ptr() + \internal +*/ + +QT_END_NAMESPACE diff --git a/src/corelib/io/qurl.h b/src/corelib/io/qurl.h new file mode 100644 index 0000000..9242092 --- /dev/null +++ b/src/corelib/io/qurl.h @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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> + +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, + + StripTrailingSlash = 0x10000 + }; + Q_DECLARE_FLAGS(FormattingOptions, FormattingOption) + + QUrl(); + 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 ©); + QUrl &operator =(const QString &url); + ~QUrl(); + + 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; + + 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 + + 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() + QLatin1String("/") + 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; } +}; + +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 0000000..5971987 --- /dev/null +++ b/src/corelib/io/qwindowspipewriter.cpp @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowspipewriter_p.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 = {0, 0, 0, 0, NULL}; + 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; + // Write 2k at a time to prevent flooding the pipe. If you + // write too much (4k-8k), the pipe can close + // unexpectedly. + if (!WriteFile(writePipe, ptrData + totalWritten, + qMin<int>(2048, 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 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 0000000..ce0c7f9 --- /dev/null +++ b/src/corelib/io/qwindowspipewriter_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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(); + +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 |