Skip to content

Instantly share code, notes, and snippets.

@suma
Created December 14, 2010 23:46
Show Gist options
  • Save suma/741359 to your computer and use it in GitHub Desktop.
Save suma/741359 to your computer and use it in GitHub Desktop.
Document::writeのDOCTYPE_BUFFER/DOCTYPE_ORIGINALを分離したいが..
#include <limits>
#include <QFile>
#include <QDataStream>
#include <QUndoStack>
#include "document.h"
#include "filemapreader.h"
const size_t Document::DEFAULT_BUFFER_SIZE = 0x100000;
class EmptyOriginal : public DocumentOriginal
{
public:
quint64 length() const
{
return 0;
}
void get(quint64, uchar *, quint64) const
{
// TODO: throw Exception
}
};
class FileOriginal : public DocumentOriginal
{
protected:
size_t buffer_size_;
QFile *file_;
// pointer to mapped
mutable uchar *ptr_;
// mapped offset and size
mutable quint64 offset_;
mutable quint64 size_;
public:
FileOriginal(QFile *file, size_t buffer_size = Document::DEFAULT_BUFFER_SIZE)
: buffer_size_(buffer_size) // buffer_size = 0x10 ^ N
, file_(file)
, ptr_(NULL)
, offset_(0)
, size_(0)
{
// check buffer_size
uint size;
for (size = 1; size < 0x10000000; size <<= 1) {
if (size == buffer_size) {
break;
}
}
if (size == 0x10000000) {
buffer_size_ = (uint)Document::DEFAULT_BUFFER_SIZE;
}
if (length() > 0) {
remap(0);
}
}
virtual ~FileOriginal()
{
delete file_;
}
quint64 length() const
{
return file_->size();
}
void get(quint64 pos, uchar *buf, quint64 len) const
{
Q_ASSERT(pos <= length());
Q_ASSERT(len <= length());
Q_ASSERT(pos <= length() - len);
while (len > 0) {
if (!isOffsetCovered(pos)) {
remap(pos);
}
// get start position
uint start = static_cast<uint>(pos & (buffer_size_ - 1));
// get copy size
uint copy_size = qMin((quint64)size_ - start, len);
// copy to memory
memcpy(buf, ptr_ + start, copy_size);
pos += copy_size;
len -= copy_size;
}
}
private:
void remap(quint64 pos) const
{
Q_ASSERT(pos < length());
if (ptr_ != NULL) {
file_->unmap(ptr_);
}
offset_ = pos & ~(buffer_size_ - 1);
size_ = qMin(length() - offset_, (quint64)buffer_size_);
ptr_ = file_->map(offset_, size_);
}
bool isOffsetCovered(quint64 pos) const
{
Q_ASSERT(pos <= length());
return offset_ <= pos && pos - offset_ <= size_;
}
};
bool Document::WriteCallback::writeCallback(quint64)
{
return true;
}
void Document::WriteCallback::writeCompleted()
{
}
class Document::FragmentCopier
{
public:
FragmentCopier(const Document *doc, uchar *buf)
: document_(doc)
, buf_(buf)
{
}
FragmentCopier &operator=(DocumentFragment fragment)
{
document_->copy(fragment.type(), fragment.position(), fragment.length(), buf_);
buf_ += fragment.length();
return *this;
}
FragmentCopier &operator*()
{
return *this;
}
FragmentCopier &operator++()
{
return *this;
}
FragmentCopier &operator++(int)
{
return *this;
}
private:
const Document *document_;
uchar *buf_;
};
Document::Document()
: impl_(new DocumentImpl())
, original_(new EmptyOriginal())
, file_(NULL)
, undo_stack_(new QUndoStack(this))
{
}
Document::Document(QFile *file)
: impl_(new DocumentImpl())
, original_(new FileOriginal(file))
, file_(file)
, undo_stack_(new QUndoStack(this))
{
impl_->insert_data(0, 0, file->size(), DOCTYPE_ORIGINAL);
}
Document::Document(QFile *file, uint buffer_size)
: impl_(new DocumentImpl())
, original_(new FileOriginal(file, buffer_size))
, file_(file)
, undo_stack_(new QUndoStack(this))
{
impl_->insert_data(0, 0, file->size(), DOCTYPE_ORIGINAL);
}
Document::~Document()
{
delete undo_stack_;
delete file_;
delete impl_;
delete original_;
}
quint64 Document::length() const
{
return impl_->length();
}
void Document::get(quint64 pos, uchar *buf, uint len) const
{
get(pos, len, FragmentCopier(this, buf));
}
void Document::copy(uint type, quint64 pos, quint64 len, uchar *buf) const
{
Q_ASSERT(buf != NULL);
switch (type) {
case DOCTYPE_BUFFER:
Q_ASSERT(pos <= buffer_.size());
Q_ASSERT(len <= buffer_.size());
Q_ASSERT(pos <= buffer_.size() - len);
memcpy(buf, &buffer_[static_cast<uint>(pos)], len);
break;
case DOCTYPE_ORIGINAL:
Q_ASSERT(pos <= original_->length());
Q_ASSERT(len <= original_->length());
Q_ASSERT(pos <= original_->length() - len);
original_->get(pos, buf, len);
break;
default:
;
}
}
void Document::insert(quint64 pos, const uchar *buf, uint len)
{
Q_ASSERT(buf != NULL);
Q_ASSERT(len != 0);
Q_ASSERT(pos <= length());
const quint64 bufPos = buffer_.size();
buffer_.insert(buffer_.end(), buf, buf + len);
impl_->insert_data(pos, bufPos, len, DOCTYPE_BUFFER);
emit inserted(pos, static_cast<quint64>(len));
}
void Document::insert(quint64 pos, DocumentFragment fragment)
{
impl_->insert_data(pos, fragment.position(), fragment.length(), fragment.type());
emit inserted(pos, fragment.length());
}
void Document::remove(quint64 pos, quint64 len)
{
Q_ASSERT(pos <= length());
Q_ASSERT(len <= length());
Q_ASSERT(pos <= length() - len);
impl_->remove_data(pos, len);
emit removed(pos, len);
}
Document::Buffer &Document::buffer()
{
return buffer_;
}
QUndoStack *Document::undoStack() const
{
return undo_stack_;
}
bool Document::write(WriteCallback *callback)
{
// TODO: check file_->name() can be written
//
// write tempfile
// move tempfile to file_->name()
// file_ reopen and reconstruct piecetable
return true;
}
// 実装中...
bool Document::write(QFile *out, WriteCallback *callback)
{
if (out == NULL) {
return false;
}
// TODO: check file_->name() can be written
QDataStream outStream(out);
FragmentList fragments;
get(0, length(), std::back_inserter(fragments));
FragmentList::iterator it = fragments.begin();
const uint BLOCK_SIZE = 1024 * 1024 * 4;
quint64 completed = 0;
FileMapReader *reader = NULL;
if (!out->seek(0)) {
goto label_error;
}
reader = new FileMapReader(NULL, BLOCK_SIZE); // FIXME: FileOriginal.file()
// piece table
while (it != fragments.end()) {
uchar *data = NULL;
if (it->type() == DOCTYPE_BUFFER) {
data = &buffer_[static_cast<uint>(it->position())];
} else if (it->type() == DOCTYPE_ORIGINAL) {
reader->seek(it->position());
}
// write piece buffer
for (quint64 len = it->length(); len != 0; ) {
uint copy_size = static_cast<uint>(qMin((quint64)BLOCK_SIZE, len));
if (it->type() == DOCTYPE_ORIGINAL) {
data = reader->get();
}
// write block
int res = outStream.writeRawData(reinterpret_cast<char*>(data), copy_size);
if (res == -1) {
// エラーでござる
goto label_error;
}
size_t wrote_size = static_cast<size_t>(res);
// increment blocks
completed += wrote_size;
len -= wrote_size;
if (it->type() == DOCTYPE_BUFFER) {
data += wrote_size;
} else if (it->type() == DOCTYPE_ORIGINAL) {
*reader += wrote_size;
}
// callback
if (callback != NULL && !callback->writeCallback(completed)) {
goto label_cancel;
}
}
++it;
}
if (callback != NULL) {
callback->writeCompleted();
}
delete reader;
return true;
label_error:
// handling error
delete reader;
return false;
label_cancel:
// Delete file
delete reader;
return false;
}
#pragma once
#include <QObject>
#include <QString>
#include <vector>
#include "document_i.h"
class DocumentImpl;
class QFile;
class QUndoStack;
class DocumentFragment
{
private:
uint type_;
quint64 position_;
quint64 length_;
public:
DocumentFragment(uint type, quint64 pos, quint64 len)
: type_(type)
, position_(pos)
, length_(len)
{
}
uint type() const
{
return type_;
}
quint64 position() const
{
return position_;
}
quint64 length() const
{
return length_;
}
};
class DocumentOriginal
{
public:
virtual quint64 length() const = 0;
virtual void get(quint64 pos, uchar *buf, quint64 len) const = 0;
};
class Document : public QObject
{
Q_OBJECT
public:
typedef std::vector<uchar> Buffer;
typedef std::vector<DocumentFragment> FragmentList;
enum {
DOCTYPE_BUFFER = 0,
DOCTYPE_ORIGINAL = 1,
};
class WriteCallback
{
public:
virtual bool writeCallback(quint64 completed);
virtual void writeCompleted();
};
public:
// 空で作成
Document();
// ファイルから開く
Document(QFile *file);
Document(QFile *file, uint buffer_size);
// 既存のドキュメントをコピー
Document(const Document &doc, bool writemode);
// TODO: static
// static Document * openFile(QFile*file);
// static Document * create();
virtual ~Document();
//-- immutable methods
//
quint64 length() const;
// copy buffer
void get(quint64 pos, uchar *buf, uint len) const;
// copy DocumentFragment from piece table
template <class T>
T get(quint64 pos, quint64 len, T result) const
{
Q_ASSERT(pos <= length());
Q_ASSERT(len <= length());
Q_ASSERT(pos <= length() - len);
uint x = impl_->documents_.findNode(pos);
Q_ASSERT(x != 0);
const quint64 diff = pos - impl_->documents_.position(x);
if (diff) {
const quint64 fragmentSize = impl_->documents_.size(x) - diff;
DocumentData *X = impl_->documents_.fragment(x);
if (fragmentSize < len) {
*result = DocumentFragment(X->type, X->bufferPosition + diff, fragmentSize);
++result;
len -= fragmentSize;
x = impl_->documents_.next(x);
} else {
*result = DocumentFragment(X->type, X->bufferPosition + diff, len);
return result;
}
}
Q_ASSERT(x != 0);
while (0 < len) {
const quint64 fragmentSize = impl_->documents_.size(x);
const uint copy_size = (static_cast<quint64>(len) < fragmentSize) ? len : static_cast<uint>(fragmentSize);
const DocumentData *X = impl_->documents_.fragment(x);
*result = DocumentFragment(X->type, X->bufferPosition, copy_size);
++result;
len -= copy_size;
x = impl_->documents_.next(x);
}
return result;
}
bool write(WriteCallback *callback);
bool write(QFile *out, WriteCallback *callback);
//-- mutable methods
void insert(quint64 pos, const uchar *buf, uint len);
// file method
// save, saveAs, ....
//
//
QUndoStack *undoStack() const;
Buffer &buffer();
public slots:
//-- mutable methods
void insert(quint64 pos, DocumentFragment fragment);
void remove(quint64 pos, quint64 len);
signals:
void inserted(quint64 pos, quint64 len);
void removed(quint64 pos, quint64 len);
public:
const static size_t DEFAULT_BUFFER_SIZE;
private:
// ドキュメントの実体をバッファへコピーする
void copy(uint type, quint64 pos, quint64 len, uchar *buf) const;
// Fragment Copy iterator
class FragmentCopier;
// Document Buffer Writer
class BufferWriter;
protected:
DocumentImpl *impl_;
DocumentOriginal *original_;
QFile *file_;
QUndoStack *undo_stack_;
Buffer buffer_;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment