mirror of
https://github.com/ariya/phantomjs.git
synced 2024-09-17 15:47:09 +03:00
Filesystem module should allow reading and writing binary files.
CommonJS proposal: http://wiki.commonjs.org/wiki/Filesystem/A. It's called "raw". http://code.google.com/p/phantomjs/issues/detail?id=400 Squashed commit of the following: commitdd5fab4778
Author: Milian Wolff <milian.wolff@kdab.com> Date: Thu Feb 23 16:19:21 2012 +0100 the "mode" string is now properly parsed, and not only the first char evaluated. This allows us to do fancy things like fs.open(file, "rw+"); // read/write/append Furthermore .read() is adapted such that it will always return the full file contents, no matter where we have seeked to before (i.e. by passing + we seek to the end, hence read() would always return an empty string). To open a binary file, pass "b" in the mode string to fs.open, e.g.: fs.open(file, "rb"); // read binary fs.open(file, "wb"); // write binary fs.open(file, "rwb+"); // read/write binary, append alternatively, one can use these shortcuts: fs.write(file, contents, "b"); // write binary fs.read(file, "b"); // read binary Unit tests are extended and the echoToFile.js example fixed (it did not close the file, which lead to the contents never getting written on-disk since flush() is never called). Also note that the FileSystem::open method was cleaned up and at least one memory leak (if QFile* could not open) was fixed. The code should now also be more C++-like. commit4113995113
Author: Milian Wolff <milian.wolff@kdab.com> Date: Wed Feb 15 16:39:23 2012 +0100 use QString instead of QByteArray for raw binary data QByteArray is simply unusable in JavaScript, since functions like e.g. window.btoa expect a string. Also there is no sane way to create a byte array in javascript, as ArrayBuffer e.g. is not supported by QScript (at least there is no conversion in place). If we use QString and some custom read/write code this all works as expected though, we can use window.btoa to base64 encode binary data and we can create random binary data using String.fromCharCode also adds a unit test commite45673486e
Author: Milian Wolff <milian.wolff@kdab.com> Date: Wed Feb 15 14:39:15 2012 +0100 make it possible to read/write raw/binary files this adds File::readRaw and File::writeRaw functions, as well as 'shimmed' versions FS::readRaw and FS::writeRaw these functions directly use QFile and QByteArray instead of QTextStream and QString, making it possible to read and write binary data, e.g. images and such.
This commit is contained in:
parent
e88e008ecf
commit
61eb86782b
@ -14,6 +14,7 @@ if (phantom.args.length < 2) {
|
||||
try {
|
||||
f = fs.open(phantom.args[0], "w");
|
||||
f.writeLine(content);
|
||||
f.close();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
@ -39,13 +39,12 @@
|
||||
// public:
|
||||
File::File(QFile *openfile, QTextCodec *codec, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_file(openfile)
|
||||
m_file(openfile),
|
||||
m_fileStream(0)
|
||||
{
|
||||
m_fileStream.setDevice(m_file);
|
||||
if ((QTextCodec *)NULL == codec) {
|
||||
m_fileStream.setCodec(QTextCodec::codecForName("UTF-8"));
|
||||
} else {
|
||||
m_fileStream.setCodec(codec);
|
||||
if ( codec ) {
|
||||
m_fileStream = new QTextStream(m_file);
|
||||
m_fileStream->setCodec(codec);
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,33 +53,80 @@ 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()
|
||||
{
|
||||
if ( m_file->isReadable() ) {
|
||||
return m_fileStream.readAll();
|
||||
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
|
||||
const qint64 pos = m_fileStream->pos();
|
||||
m_fileStream->seek(0);
|
||||
const QString ret = m_fileStream->readAll();
|
||||
m_fileStream->seek(pos);
|
||||
return ret;
|
||||
} else {
|
||||
// binary file
|
||||
const qint64 pos = m_file->pos();
|
||||
m_file->seek(0);
|
||||
const QByteArray data = m_file->readAll();
|
||||
m_file->seek(pos);
|
||||
QString ret(data.size());
|
||||
for(int i = 0; i < data.size(); ++i) {
|
||||
ret[i] = data.at(i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
qDebug() << "File::read - " << "Couldn't read:" << m_file->fileName();
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool File::write(const QString &data)
|
||||
{
|
||||
if ( m_file->isWritable() ) {
|
||||
m_fileStream << data;
|
||||
if ( !m_file->isWritable() ) {
|
||||
qDebug() << "File::write - " << "Couldn't write:" << m_file->fileName();
|
||||
return true;
|
||||
}
|
||||
qDebug() << "File::write - " << "Couldn't write:" << m_file->fileName();
|
||||
return false;
|
||||
if ( m_fileStream ) {
|
||||
// text file
|
||||
(*m_fileStream) << data;
|
||||
return true;
|
||||
} else {
|
||||
// binary file
|
||||
QByteArray bytes(data.size(), Qt::Uninitialized);
|
||||
for(int i = 0; i < data.size(); ++i) {
|
||||
bytes[i] = data.at(i).toAscii();
|
||||
}
|
||||
return m_file->write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
QString File::readLine()
|
||||
{
|
||||
if ( m_file->isReadable() ) {
|
||||
return m_fileStream.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::fromAscii(m_file->readLine());
|
||||
}
|
||||
qDebug() << "File::readLine - " << "Couldn't read:" << m_file->fileName();
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool File::writeLine(const QString &data)
|
||||
@ -95,7 +141,13 @@ bool File::writeLine(const QString &data)
|
||||
bool File::atEnd() const
|
||||
{
|
||||
if ( m_file->isReadable() ) {
|
||||
return m_fileStream.atEnd();
|
||||
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;
|
||||
@ -104,13 +156,22 @@ bool File::atEnd() const
|
||||
void File::flush()
|
||||
{
|
||||
if ( m_file ) {
|
||||
m_fileStream.flush();
|
||||
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;
|
||||
@ -278,95 +339,81 @@ QString FileSystem::absolute(const QString &relativePath) const
|
||||
return QFileInfo(relativePath).absoluteFilePath();
|
||||
}
|
||||
|
||||
static inline QString getCharset(const QVariant &val) {
|
||||
QVariant::Type type = val.type();
|
||||
|
||||
// val must be either a string or null/undefined.
|
||||
if (QVariant::String != type && QVariant::Invalid != type) {
|
||||
qDebug() << "FileSystem::open - " << "Charset must be a string!";
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString charset = val.toString();
|
||||
|
||||
// Default to UTF-8
|
||||
if (charset.isEmpty()) {
|
||||
charset = "UTF-8";
|
||||
}
|
||||
|
||||
return charset;
|
||||
}
|
||||
|
||||
// Files
|
||||
QObject *FileSystem::_open(const QString &path, const QVariantMap &opts) const
|
||||
{
|
||||
File *f = NULL;
|
||||
QFile *_f = new QFile(path);
|
||||
QFile::OpenMode modeCode = QFile::NotOpen;
|
||||
QVariant modeVar = opts["mode"];
|
||||
|
||||
const QVariant modeVar = opts["mode"];
|
||||
// Ensure only strings
|
||||
if (modeVar.type() != QVariant::String) {
|
||||
qDebug() << "FileSystem::open - " << "Mode must be a string!" << modeVar;
|
||||
return NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString mode = modeVar.toString();
|
||||
|
||||
// Ensure only one "mode character" has been selected
|
||||
if ( mode.length() != 1) {
|
||||
qDebug() << "FileSystem::open - " << "Wrong Mode string length:" << mode;
|
||||
return NULL;
|
||||
}
|
||||
bool isBinary = false;
|
||||
QFile::OpenMode modeCode = QFile::NotOpen;
|
||||
|
||||
// Determine the OpenMode
|
||||
switch(mode[0].toAscii()) {
|
||||
case 'r': case 'R': {
|
||||
modeCode |= QFile::ReadOnly;
|
||||
// Make sure there is something to read
|
||||
if ( !_f->exists() ) {
|
||||
qDebug() << "FileSystem::open - " << "Trying to read a file that doesn't exist:" << path;
|
||||
return NULL;
|
||||
foreach(const QChar &c, modeVar.toString()) {
|
||||
switch(c.toAscii()) {
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'a': case 'A': case '+': {
|
||||
modeCode |= QFile::Append;
|
||||
// NOTE: no "break" here! This case will also execute the code for case 'w'.
|
||||
}
|
||||
case 'w': case 'W': {
|
||||
modeCode |= QFile::WriteOnly;
|
||||
// Make sure the file exists OR it can be created at the required path
|
||||
if ( !_f->exists() && !makeTree(QFileInfo(path).dir().absolutePath()) ) {
|
||||
qDebug() << "FileSystem::open - " << "Full path coulnd't be created:" << path;
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
qDebug() << "FileSystem::open - " << "Wrong Mode:" << mode;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
QString charset = getCharset(opts["charset"]);
|
||||
QTextCodec *codec = QTextCodec::codecForName(charset.toAscii());
|
||||
if ((QTextCodec *)NULL == codec) {
|
||||
qDebug() << "FileSystem::open - " << "Unknown charset:" << charset;
|
||||
return NULL;
|
||||
// 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.toAscii());
|
||||
if (!codec) {
|
||||
qDebug() << "FileSystem::open - " << "Unknown charset:" << charset;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to Open
|
||||
if ( _f->open(modeCode) ) {
|
||||
f = new File(_f, codec);
|
||||
if ( f ) {
|
||||
return f;
|
||||
}
|
||||
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 "NULL" if the file couldn't be opened as requested
|
||||
qDebug() << "FileSystem::open - " << "Couldn't be opened:" << path;
|
||||
return NULL;
|
||||
return new File(file, codec);
|
||||
}
|
||||
|
||||
bool FileSystem::_remove(const QString &path) const
|
||||
|
@ -42,6 +42,8 @@ class File : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// handle a textfile with given codec
|
||||
// if @p codec is null, the file is considered to be binary
|
||||
File(QFile *openfile, QTextCodec *codec, QObject *parent = 0);
|
||||
virtual ~File();
|
||||
|
||||
@ -58,7 +60,7 @@ public slots:
|
||||
|
||||
private:
|
||||
QFile *m_file;
|
||||
QTextStream m_fileStream;
|
||||
QTextStream *m_fileStream;
|
||||
};
|
||||
|
||||
|
||||
@ -91,7 +93,9 @@ public slots:
|
||||
// 'open(path, mode|options)' implemented in "filesystem-shim.js" using '_open(path, opts)'
|
||||
QObject *_open(const QString &path, const QVariantMap &opts) const;
|
||||
// 'read(path, options)' implemented in "filesystem-shim.js"
|
||||
// 'readRaw(path, options)' implemented in "filesystem-shim.js"
|
||||
// 'write(path, mode|options)' implemented in the "filesystem-shim.js"
|
||||
// 'writeRaw(path, mode|options)' implemented in the "filesystem-shim.js"
|
||||
// 'remove(path)' implemented in "filesystem-shim.js" using '_remove(path)'
|
||||
bool _remove(const QString &path) const;
|
||||
// 'copy(source, destination)' implemented in "filesystem-shim.js" using '_copy(source, destination)'
|
||||
|
@ -32,19 +32,16 @@
|
||||
|
||||
// JavaScript "shim" to throw exceptions in case a critical operation fails.
|
||||
|
||||
/** Open and return a "file" object.
|
||||
* It will throw exception if it fails.
|
||||
/** Convert a modeOrOpts to a map
|
||||
*
|
||||
* @param path Path of the file to open
|
||||
* @param modeOrOpts
|
||||
* mode: Open Mode. A string made of 'r', 'w', 'a/+' characters.
|
||||
* mode: Open Mode. A string made of 'r', 'w', 'a/+', 'b' characters.
|
||||
* opts: Options.
|
||||
* - mode (see Open Mode above)
|
||||
* - charset An IANA, case insensitive, charset name.
|
||||
* @return "file" object
|
||||
*/
|
||||
exports.open = function (path, modeOrOpts) {
|
||||
var file, opts;
|
||||
function modeOrOptsToOpts(modeOrOpts) {
|
||||
var opts;
|
||||
|
||||
// Extract charset from opts
|
||||
if (modeOrOpts == null) {
|
||||
@ -58,27 +55,57 @@ exports.open = function (path, modeOrOpts) {
|
||||
opts = modeOrOpts;
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
/** Open and return a "file" object.
|
||||
* It will throw exception if it fails.
|
||||
*
|
||||
* @param path Path of the file to open
|
||||
* @param modeOrOpts
|
||||
* mode: Open Mode. A string made of 'r', 'w', 'a/+', 'b' characters.
|
||||
* opts: Options.
|
||||
* - mode (see Open Mode above)
|
||||
* - charset An IANA, case insensitive, charset name.
|
||||
* @return "file" object
|
||||
*/
|
||||
exports.open = function (path, modeOrOpts) {
|
||||
// Open file
|
||||
file = exports._open(path, opts);
|
||||
var file = exports._open(path, modeOrOptsToOpts(modeOrOpts));
|
||||
if (file) {
|
||||
return file;
|
||||
}
|
||||
throw "Unable to open file '" + path + "'";
|
||||
};
|
||||
|
||||
/** Open, read and return content of a file.
|
||||
/** Open, read and return text content of a file.
|
||||
* It will throw an exception if it fails.
|
||||
*
|
||||
* @param path Path of the file to read from
|
||||
* @param opts Options.
|
||||
* - charset An IANA, case insensitive, charset name.
|
||||
* @param modeOrOpts
|
||||
* mode: Open Mode. 'b' to open a raw binary file
|
||||
* opts: Options.
|
||||
* - mode (see Open Mode above)
|
||||
* - charset An IANA, case insensitive, charset name.
|
||||
* @return file content
|
||||
*/
|
||||
exports.read = function (path, opts) {
|
||||
if (opts == null || typeof opts !== 'object') {
|
||||
opts = {};
|
||||
exports.read = function (path, modeOrOpts) {
|
||||
if (typeof modeOrOpts == 'string') {
|
||||
if (modeOrOpts.toLowerCase() == 'b') {
|
||||
// open binary
|
||||
modeOrOpts = {mode: modeOrOpts};
|
||||
} else {
|
||||
// asume charset is given
|
||||
modeOrOpts = {charset: modeOrOpts};
|
||||
}
|
||||
}
|
||||
var opts = modeOrOptsToOpts(modeOrOpts);
|
||||
// ensure we open for reading
|
||||
if ( typeof opts.mode !== 'string' ) {
|
||||
opts.mode = 'r';
|
||||
} else if ( opts.mode.indexOf('r') == -1 ) {
|
||||
opts.mode += 'r';
|
||||
}
|
||||
opts.mode = 'r';
|
||||
var f = exports.open(path, opts),
|
||||
content = f.read();
|
||||
|
||||
@ -86,22 +113,26 @@ exports.read = function (path, opts) {
|
||||
return content;
|
||||
};
|
||||
|
||||
/** Open and write content to a file
|
||||
/** Open and write text content to a file
|
||||
* It will throw an exception if it fails.
|
||||
*
|
||||
* @param path Path of the file to read from
|
||||
* @param content Content to write to the file
|
||||
* @param modeOrOpts
|
||||
* mode: Open Mode. A string made of 'r', 'w', 'a/+' characters.
|
||||
* mode: Open Mode. A string made of 'r', 'w', 'a/+', 'b' characters.
|
||||
* opts: Options.
|
||||
* - mode (see Open Mode above)
|
||||
* - charset An IANA, case insensitive, charset name.
|
||||
*/
|
||||
exports.write = function (path, content, modeOrOpts) {
|
||||
if (modeOrOpts == null) {
|
||||
modeOrOpts = {};
|
||||
var opts = modeOrOptsToOpts(modeOrOpts);
|
||||
// ensure we open for writing
|
||||
if ( typeof opts.mode !== 'string' ) {
|
||||
opts.mode = 'w';
|
||||
} else if ( opts.mode.indexOf('w') == -1 ) {
|
||||
opts.mode += 'w';
|
||||
}
|
||||
var f = exports.open(path, modeOrOpts);
|
||||
var f = exports.open(path, opts);
|
||||
|
||||
f.write(content);
|
||||
f.close();
|
||||
|
@ -4,6 +4,7 @@ describe("Basic Files API (read, write, remove, ...)", function() {
|
||||
FILENAME_MOVED = FILENAME + ".moved",
|
||||
FILENAME_EMPTY = FILENAME + ".empty",
|
||||
FILENAME_ENC = FILENAME + ".enc",
|
||||
FILENAME_BIN = FILENAME + ".bin",
|
||||
ABSENT = "absent-01.test";
|
||||
|
||||
it("should be able to create and write a file", function() {
|
||||
@ -36,6 +37,19 @@ describe("Basic Files API (read, write, remove, ...)", function() {
|
||||
expect(content).toEqual("hello\nworld\n");
|
||||
});
|
||||
|
||||
it("should be able to read/write/append content from a file", function() {
|
||||
var content = "";
|
||||
try{
|
||||
var f = fs.open(FILENAME, "rw+");
|
||||
console.log(f.read().length);
|
||||
f.writeLine("asdf");
|
||||
content = f.read();
|
||||
console.log(content.length);
|
||||
f.close();
|
||||
} catch (e) { }
|
||||
expect(content).toEqual("hello\nworld\nasdf\n");
|
||||
});
|
||||
|
||||
it("should be able to copy a file", function() {
|
||||
expect(fs.exists(FILENAME_COPY)).toBeFalsy();
|
||||
fs.copy(FILENAME, FILENAME_COPY);
|
||||
@ -97,4 +111,32 @@ describe("Basic Files API (read, write, remove, ...)", function() {
|
||||
} catch (e) { }
|
||||
expect(content).toEqual(output);
|
||||
});
|
||||
|
||||
it("should be read/write binary data", function() {
|
||||
var content, output = String.fromCharCode(0, 1, 2, 3, 4, 5);
|
||||
try {
|
||||
var f = fs.open(FILENAME_BIN, "wb");
|
||||
f.write(output);
|
||||
f.close();
|
||||
|
||||
f = fs.open(FILENAME_BIN, "rb");
|
||||
content = f.read();
|
||||
f.close();
|
||||
|
||||
fs.remove(FILENAME_BIN);
|
||||
} catch (e) { }
|
||||
expect(content).toEqual(output);
|
||||
});
|
||||
|
||||
it("should be read/write binary data (shortcuts)", function() {
|
||||
var content, output = String.fromCharCode(0, 1, 2, 3, 4, 5);
|
||||
try {
|
||||
fs.write(FILENAME_BIN, output, "b");
|
||||
|
||||
content = fs.read(FILENAME_BIN, "b");
|
||||
|
||||
fs.remove(FILENAME_BIN);
|
||||
} catch (e) { }
|
||||
expect(content).toEqual(output);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user