debian-phantomjs/src/filesystem.cpp

539 lines
14 KiB
C++

/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ivan De Marino <ivan.de.marino@gmail.com>
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 <organization> 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 <COPYRIGHT HOLDER> 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 <QFile>
#include <QFileInfo>
#include <QDir>
#include <QDebug>
#include <QDateTime>
// 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);
}