/* This file is part of the PhantomJS project from Ofi Labs. Copyright (C) 2011 Ivan De Marino Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the 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 COPYRIGHT HOLDERS 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 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. */ #include "filesystem.h" #include #include #include #include #include // File // public: File::File(QFile *openfile, QTextCodec *codec, QObject *parent) : QObject(parent), m_file(openfile), m_fileStream(0) { if ( codec ) { m_fileStream = new QTextStream(m_file); m_fileStream->setCodec(codec); } } File::~File() { this->close(); } //NOTE: for binary files we want to use QString instead of QByteArray as the // latter is not really useable in javascript and e.g. window.btoa expects a string // and we need special code required since fromAsci() would stop as soon as it // encounters \0 or similar // public slots: QString File::read(const QVariant &n) { // Default to 1024 (used when n is "null") qint64 bytesToRead = 1024; // If parameter can be converted to a qint64, do so and use that value instead if (n.canConvert(QVariant::LongLong)) { bytesToRead = n.toLongLong(); } const bool isReadAll = 0 > bytesToRead; if ( !m_file->isReadable() ) { qDebug() << "File::read - " << "Couldn't read:" << m_file->fileName(); return QString(); } if ( m_file->isWritable() ) { // make sure we write everything to disk before reading flush(); } if ( m_fileStream ) { // text file QString ret; if (isReadAll) { // This code, for some reason, reads the whole file from 0 to EOF, // and then resets to the position the file was at prior to reading const qint64 pos = m_fileStream->pos(); m_fileStream->seek(0); ret = m_fileStream->readAll(); m_fileStream->seek(pos); } else { ret = m_fileStream->read(bytesToRead); } return ret; } else { // binary file QByteArray data; if (isReadAll) { // This code, for some reason, reads the whole file from 0 to EOF, // and then resets to the position the file was at prior to reading const qint64 pos = m_file->pos(); m_file->seek(0); data = m_file->readAll(); m_file->seek(pos); } else { data = m_file->read(bytesToRead); } QString ret(data.size()); for(int i = 0; i < data.size(); ++i) { ret[i] = data.at(i); } return ret; } } bool File::write(const QString &data) { if ( !m_file->isWritable() ) { qDebug() << "File::write - " << "Couldn't write:" << m_file->fileName(); return true; } if ( m_fileStream ) { // text file (*m_fileStream) << data; if (_isUnbuffered()) { m_fileStream->flush(); } return true; } else { // binary file QByteArray bytes(data.size(), Qt::Uninitialized); for(int i = 0; i < data.size(); ++i) { bytes[i] = data.at(i).toLatin1(); } return m_file->write(bytes); } } bool File::seek(const qint64 pos) { if (m_fileStream) { return m_fileStream->seek(pos); } else { return m_file->seek(pos); } } QString File::readLine() { if ( !m_file->isReadable() ) { qDebug() << "File::readLine - " << "Couldn't read:" << m_file->fileName(); return QString(); } if ( m_file->isWritable() ) { // make sure we write everything to disk before reading flush(); } if ( m_fileStream ) { // text file return m_fileStream->readLine(); } else { // binary file - doesn't make much sense but well... return QString::fromLatin1(m_file->readLine()); } } bool File::writeLine(const QString &data) { if ( write(data) && write("\n") ) { return true; } qDebug() << "File::writeLine - " << "Couldn't write:" << m_file->fileName(); return false; } bool File::atEnd() const { if ( m_file->isReadable() ) { if (m_fileStream) { // text file return m_fileStream->atEnd(); } else { // binary file return m_file->atEnd(); } } qDebug() << "File::atEnd - " << "Couldn't read:" << m_file->fileName(); return false; } void File::flush() { if ( m_file ) { if ( m_fileStream ) { // text file m_fileStream->flush(); } // binary or text file m_file->flush(); } } void File::close() { flush(); if ( m_fileStream ) { delete m_fileStream; m_fileStream = 0; } if ( m_file ) { m_file->close(); delete m_file; m_file = NULL; } deleteLater(); } bool File::setEncoding(const QString &encoding) { if (encoding.isEmpty() || encoding.isNull()) { return false; } // "Binary" mode doesn't use/need text codecs if ((QTextStream *)NULL == m_fileStream) { // TODO: Should we switch to "text" mode? return false; } // Since there can be multiple names for the same codec (i.e., "utf8" and // "utf-8"), we need to get the codec in the system first and use its // canonical name QTextCodec *codec = QTextCodec::codecForName(encoding.toLatin1()); if ((QTextCodec *)NULL == codec) { return false; } // Check whether encoding actually needs to be changed const QString encodingBeforeUpdate(m_fileStream->codec()->name()); if (0 == encodingBeforeUpdate.compare(QString(codec->name()), Qt::CaseInsensitive)) { return false; } m_fileStream->setCodec(codec); // Return whether update was successful const QString encodingAfterUpdate(m_fileStream->codec()->name()); return 0 != encodingBeforeUpdate.compare(encodingAfterUpdate, Qt::CaseInsensitive); } QString File::getEncoding() const { QString encoding; if ((QTextStream *)NULL != m_fileStream) { encoding = QString(m_fileStream->codec()->name()); } return encoding; } // private: bool File::_isUnbuffered() const { return m_file->openMode() & QIODevice::Unbuffered; } // FileSystem // public: FileSystem::FileSystem(QObject *parent) : QObject(parent) { } // public slots: // Attributes int FileSystem::_size(const QString &path) const { QFileInfo fi(path); if (fi.exists()) { return fi.size(); } return -1; } QVariant FileSystem::lastModified(const QString &path) const { QFileInfo fi(path); if (fi.exists()) { return QVariant(fi.lastModified()); } return QVariant(QDateTime()); } // Links QString FileSystem::readLink(const QString &path) const { return QFileInfo(path).symLinkTarget(); } // Tests bool FileSystem::exists(const QString &path) const { return QFile::exists(path); } bool FileSystem::isDirectory(const QString &path) const { return QFileInfo(path).isDir(); } bool FileSystem::isFile(const QString &path) const { return QFileInfo(path).isFile(); } bool FileSystem::isAbsolute(const QString &path) const { return QFileInfo(path).isAbsolute(); } bool FileSystem::isExecutable(const QString &path) const { return QFileInfo(path).isExecutable(); } bool FileSystem::isLink(const QString &path) const { return QFileInfo(path).isSymLink(); } bool FileSystem::isReadable(const QString &path) const { return QFileInfo(path).isReadable(); } bool FileSystem::isWritable(const QString &path) const { return QFileInfo(path).isWritable(); } // Directory bool FileSystem::_copyTree(const QString &source, const QString &destination) const { QDir sourceDir(source); QDir::Filters sourceDirFilter = QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files | QDir::NoSymLinks | QDir::Drives; if (sourceDir.exists()) { // Make the destination directory if it doesn't exist already if (!FileSystem::exists(destination) && !FileSystem::makeDirectory(destination)) { return false; } foreach(QFileInfo entry, sourceDir.entryInfoList(sourceDirFilter, QDir::DirsFirst)) { if (entry.isDir()) { if (!FileSystem::_copyTree(entry.absoluteFilePath(), destination + "/" + entry.fileName())) { //< directory: recursive call return false; } } else { if (!FileSystem::_copy(entry.absoluteFilePath(), destination + "/" + entry.fileName())) { //< file: copy return false; } } } } return true; } bool FileSystem::makeDirectory(const QString &path) const { return QDir().mkdir(path); } bool FileSystem::makeTree(const QString &path) const { return QDir().mkpath(path); } bool FileSystem::_removeDirectory(const QString &path) const { return QDir().rmdir(path); } bool FileSystem::_removeTree(const QString &path) const { QDir dir(path); QDir::Filters dirFilter = QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files; if (dir.exists()) { foreach(QFileInfo info, dir.entryInfoList(dirFilter, QDir::DirsFirst)) { if (info.isDir()) { if (!FileSystem::_removeTree(info.absoluteFilePath())) { //< directory: recursive call return false; } } else { if (!FileSystem::_remove(info.absoluteFilePath())) { //< file: remove return false; } } } if (!FileSystem::_removeDirectory(path)) { //< delete the top tree directory return false; } } return true; } QStringList FileSystem::list(const QString &path) const { return QDir(path).entryList(); } // Paths QString FileSystem::separator() const { return QDir::separator(); } QString FileSystem::workingDirectory() const { return QDir::currentPath(); } bool FileSystem::changeWorkingDirectory(const QString &path) const { return QDir::setCurrent(path); } QString FileSystem::absolute(const QString &relativePath) const { return QFileInfo(relativePath).absoluteFilePath(); } QString FileSystem::fromNativeSeparators(const QString &path) const { return QDir::fromNativeSeparators(path); } QString FileSystem::toNativeSeparators(const QString &path) const { return QDir::toNativeSeparators(path); } // Files QObject *FileSystem::_open(const QString &path, const QVariantMap &opts) const { qDebug() << "FileSystem - _open:" << path << opts; const QVariant modeVar = opts["mode"]; // Ensure only strings if (modeVar.type() != QVariant::String) { qDebug() << "FileSystem::open - " << "Mode must be a string!" << modeVar; return 0; } bool isBinary = false; QFile::OpenMode modeCode = QFile::NotOpen; // Determine the OpenMode foreach(const QChar &c, modeVar.toString()) { switch(c.toLatin1()) { case 'r': case 'R': { modeCode |= QFile::ReadOnly; break; } case 'a': case 'A': case '+': { modeCode |= QFile::Append; modeCode |= QFile::WriteOnly; break; } case 'w': case 'W': { modeCode |= QFile::WriteOnly; break; } case 'b': case 'B': { isBinary = true; break; } default: { qDebug() << "FileSystem::open - " << "Wrong Mode:" << c; return 0; } } } // Make sure the file exists OR it can be created at the required path if ( !QFile::exists(path) && modeCode & QFile::WriteOnly ) { if ( !makeTree(QFileInfo(path).dir().absolutePath()) ) { qDebug() << "FileSystem::open - " << "Full path coulnd't be created:" << path; return 0; } } // Make sure there is something to read if ( !QFile::exists(path) && modeCode & QFile::ReadOnly ) { qDebug() << "FileSystem::open - " << "Trying to read a file that doesn't exist:" << path; return 0; } QTextCodec *codec = 0; if (!isBinary) { // default to UTF-8 encoded files const QString charset = opts.value("charset", "UTF-8").toString(); codec = QTextCodec::codecForName(charset.toLatin1()); if (!codec) { qDebug() << "FileSystem::open - " << "Unknown charset:" << charset; return 0; } } // Try to Open QFile* file = new QFile(path); if ( !file->open(modeCode) ) { // Return "NULL" if the file couldn't be opened as requested delete file; qDebug() << "FileSystem::open - " << "Couldn't be opened:" << path; return 0; } return new File(file, codec); } bool FileSystem::_remove(const QString &path) const { return QFile::remove(path); } bool FileSystem::_copy(const QString &source, const QString &destination) const { return QFile(source).copy(destination); }