mirror of
https://github.com/ONLYOFFICE/core.git
synced 2026-02-10 18:05:41 +08:00
Fixed stream seek problem. Fixed CompoundFile::Save()
This commit is contained in:
41
Common/cfcpp/RBTree/irbnode.h
Normal file
41
Common/cfcpp/RBTree/irbnode.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
//#include "../../DocxFormat/Source/Base/Nullable.h"
|
||||
#include <memory>
|
||||
|
||||
namespace RedBlackTree
|
||||
{
|
||||
class IRBNode;
|
||||
using PIRBNode = std::shared_ptr<IRBNode>;
|
||||
using WPIRBNode = std::weak_ptr<IRBNode>;
|
||||
|
||||
enum Color
|
||||
{
|
||||
RED = 0,
|
||||
BLACK = 1
|
||||
};
|
||||
|
||||
class IRBNode
|
||||
{
|
||||
public:
|
||||
virtual void setLeft(PIRBNode pNode) = 0;
|
||||
virtual void setRight(PIRBNode pNode) = 0;
|
||||
virtual PIRBNode getLeft() const = 0;
|
||||
virtual PIRBNode getRight() const = 0;
|
||||
|
||||
virtual void setColor(Color clr) = 0;
|
||||
virtual Color getColor()const = 0;
|
||||
|
||||
virtual void setParent(PIRBNode pParent) = 0;
|
||||
virtual PIRBNode getParent() const = 0;
|
||||
|
||||
virtual PIRBNode Grandparent() const = 0;
|
||||
virtual PIRBNode Sibling() const = 0;
|
||||
virtual PIRBNode Uncle() const = 0;
|
||||
|
||||
virtual void AssignValueTo(PIRBNode other) = 0;
|
||||
|
||||
virtual int CompareTo(const PIRBNode& other) const = 0;
|
||||
virtual std::wstring ToString() const = 0;
|
||||
};
|
||||
}
|
||||
434
Common/cfcpp/RBTree/rbtree.cpp
Normal file
434
Common/cfcpp/RBTree/rbtree.cpp
Normal file
@ -0,0 +1,434 @@
|
||||
#include "rbtree.h"
|
||||
#include "rbtreeexception.h"
|
||||
|
||||
using namespace RedBlackTree;
|
||||
|
||||
RBTree::RBTree(PIRBNode root) : root(root)
|
||||
{}
|
||||
|
||||
const PIRBNode RBTree::getRoot() const
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
void RBTree::setRoot(const PIRBNode &newRoot)
|
||||
{
|
||||
root = newRoot;
|
||||
}
|
||||
|
||||
bool RBTree::TryLookup(PIRBNode templ, PIRBNode &val)
|
||||
{
|
||||
PIRBNode n = LookupNode(templ);
|
||||
|
||||
if (n == nullptr)
|
||||
{
|
||||
val.reset();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = n;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void RBTree::Insert(PIRBNode newNode)
|
||||
{
|
||||
newNode->setColor(Color::RED);
|
||||
PIRBNode insertedNode = newNode;
|
||||
|
||||
if (getRoot() == nullptr)
|
||||
{
|
||||
setRoot(insertedNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
PIRBNode n = getRoot();
|
||||
while (true)
|
||||
{
|
||||
int compResult = newNode->CompareTo(n);
|
||||
if (compResult == 0)
|
||||
{
|
||||
throw RBTreeDuplicatedItemException(L"RBNode " + newNode->ToString() + L" already present in tree");
|
||||
//n->Value = value;
|
||||
//return;
|
||||
}
|
||||
else if (compResult < 0)
|
||||
{
|
||||
if (n->getLeft() == nullptr)
|
||||
{
|
||||
n->setLeft(insertedNode);
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
n = n->getLeft();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//assert compResult > 0;
|
||||
if (n->getRight() == nullptr)
|
||||
{
|
||||
n->setRight(insertedNode);
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
n = n->getRight();
|
||||
}
|
||||
}
|
||||
}
|
||||
insertedNode->setParent(n);
|
||||
}
|
||||
|
||||
InsertCase1(insertedNode);
|
||||
|
||||
// if (NodeInserted != nullptr)
|
||||
// {
|
||||
// NodeInserted(insertedNode);
|
||||
// }
|
||||
|
||||
//Trace.WriteLine(" ");
|
||||
//Print();
|
||||
}
|
||||
|
||||
void RBTree::Delete(PIRBNode templ, PIRBNode &deletedAlt)
|
||||
{
|
||||
deletedAlt.reset();
|
||||
PIRBNode n = LookupNode(templ);
|
||||
templ = n;
|
||||
if (n == nullptr)
|
||||
return; // Key not found, do nothing
|
||||
if (n->getLeft() != nullptr && n->getRight() != nullptr)
|
||||
{
|
||||
// Copy key/value from predecessor and then delete it instead
|
||||
PIRBNode pred = MaximumNode(n->getLeft());
|
||||
pred->AssignValueTo(n);
|
||||
n = pred;
|
||||
deletedAlt = pred;
|
||||
}
|
||||
|
||||
//assert n->left == null || n->right == null;
|
||||
PIRBNode child = (n->getRight() == nullptr) ? n->getLeft() : n->getRight();
|
||||
if (NodeColor(n) == Color::BLACK)
|
||||
{
|
||||
n->setColor(NodeColor(child));
|
||||
DeleteCase1(n);
|
||||
}
|
||||
|
||||
ReplaceNode(n, child);
|
||||
|
||||
if (NodeColor(getRoot()) == Color::RED)
|
||||
{
|
||||
getRoot()->setColor(Color::BLACK);
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void RBTree::VisitTree(Action<PIRBNode> action)
|
||||
{
|
||||
PIRBNode walker = getRoot();
|
||||
|
||||
if (walker != nullptr)
|
||||
DoVisitTree(action, walker);
|
||||
}
|
||||
|
||||
Color RBTree::NodeColor(PIRBNode n)
|
||||
{
|
||||
return n == nullptr ? Color::BLACK : n->getColor();
|
||||
}
|
||||
|
||||
PIRBNode RBTree::MaximumNode(PIRBNode n)
|
||||
{
|
||||
while (n->getRight() != nullptr)
|
||||
{
|
||||
n = n->getRight();
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
PIRBNode RBTree::LookupNode(PIRBNode templ)
|
||||
{
|
||||
PIRBNode n = getRoot();
|
||||
|
||||
while (n != nullptr)
|
||||
{
|
||||
int compResult = templ->CompareTo(n);
|
||||
|
||||
if (compResult == 0)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
else if (compResult < 0)
|
||||
{
|
||||
n = n->getLeft();
|
||||
}
|
||||
else
|
||||
{
|
||||
//assert compResult > 0;
|
||||
n = n->getRight();
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void RBTree::ReplaceNode(PIRBNode oldn, PIRBNode newn)
|
||||
{
|
||||
if (oldn->getParent() == nullptr)
|
||||
{
|
||||
setRoot(newn);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (oldn == oldn->getParent()->getLeft())
|
||||
oldn->getParent()->setLeft(newn);
|
||||
else
|
||||
oldn->getParent()->setRight(newn);
|
||||
}
|
||||
if (newn != nullptr)
|
||||
{
|
||||
newn->setParent(oldn->getParent());
|
||||
}
|
||||
}
|
||||
|
||||
void RBTree::RotateLeft(PIRBNode n)
|
||||
{
|
||||
PIRBNode r = n->getRight();
|
||||
ReplaceNode(n, r);
|
||||
n->setRight(r->getLeft());
|
||||
if (r->getLeft() != nullptr)
|
||||
{
|
||||
r->getLeft()->setParent(n);
|
||||
}
|
||||
r->setLeft(n);
|
||||
n->setParent(r);
|
||||
}
|
||||
|
||||
void RBTree::RotateRight(PIRBNode n)
|
||||
{
|
||||
PIRBNode l = n->getLeft();
|
||||
ReplaceNode(n, l);
|
||||
n->setLeft(l->getRight());
|
||||
|
||||
if (l->getRight() != nullptr)
|
||||
{
|
||||
l->getRight()->setParent(n);
|
||||
}
|
||||
|
||||
l->setRight(n);
|
||||
n->setParent(l);
|
||||
}
|
||||
|
||||
void RBTree::InsertCase1(PIRBNode n)
|
||||
{
|
||||
if (n->getParent() == nullptr)
|
||||
n->setColor(Color::BLACK);
|
||||
else
|
||||
InsertCase2(n);
|
||||
}
|
||||
|
||||
void RBTree::InsertCase2(PIRBNode n)
|
||||
{
|
||||
if (NodeColor(n->getParent()) == Color::BLACK)
|
||||
return; // Tree is still valid
|
||||
else
|
||||
InsertCase3(n);
|
||||
}
|
||||
|
||||
void RBTree::InsertCase3(PIRBNode n)
|
||||
{
|
||||
if (NodeColor(n->Uncle()) == Color::RED)
|
||||
{
|
||||
n->getParent()->setColor(Color::BLACK);
|
||||
n->Uncle()->setColor(Color::BLACK);
|
||||
n->Grandparent()->setColor(Color::RED);
|
||||
InsertCase1(n->Grandparent());
|
||||
}
|
||||
else
|
||||
{
|
||||
InsertCase4(n);
|
||||
}
|
||||
}
|
||||
|
||||
void RBTree::InsertCase4(PIRBNode n)
|
||||
{
|
||||
if (n == n->getParent()->getRight() && n->getParent() == n->Grandparent()->getLeft())
|
||||
{
|
||||
RotateLeft(n->getParent());
|
||||
n = n->getLeft();
|
||||
}
|
||||
else if (n == n->getParent()->getLeft() && n->getParent() == n->Grandparent()->getRight())
|
||||
{
|
||||
RotateRight(n->getParent());
|
||||
n = n->getRight();
|
||||
}
|
||||
|
||||
InsertCase5(n);
|
||||
}
|
||||
|
||||
void RBTree::InsertCase5(PIRBNode n)
|
||||
{
|
||||
n->getParent()->setColor(Color::BLACK);
|
||||
n->Grandparent()->setColor(Color::RED);
|
||||
if (n == n->getParent()->getLeft() && n->getParent() == n->Grandparent()->getLeft())
|
||||
{
|
||||
RotateRight(n->Grandparent());
|
||||
}
|
||||
else
|
||||
{
|
||||
//assert n == n->getParent().right && n->getParent() == n->grandparent().right;
|
||||
RotateLeft(n->Grandparent());
|
||||
}
|
||||
}
|
||||
|
||||
void RBTree::DeleteCase1(PIRBNode n)
|
||||
{
|
||||
if (n->getParent() == nullptr)
|
||||
return;
|
||||
else
|
||||
DeleteCase2(n);
|
||||
}
|
||||
|
||||
void RBTree::DeleteCase2(PIRBNode n)
|
||||
{
|
||||
if (NodeColor(n->Sibling()) == Color::RED)
|
||||
{
|
||||
n->getParent()->setColor(Color::RED);
|
||||
n->Sibling()->setColor(Color::BLACK);
|
||||
if (n == n->getParent()->getLeft())
|
||||
RotateLeft(n->getParent());
|
||||
else
|
||||
RotateRight(n->getParent());
|
||||
}
|
||||
|
||||
DeleteCase3(n);
|
||||
}
|
||||
|
||||
void RBTree::DeleteCase3(PIRBNode n)
|
||||
{
|
||||
if (NodeColor(n->getParent()) == Color::BLACK &&
|
||||
NodeColor(n->Sibling()) == Color::BLACK &&
|
||||
NodeColor(n->Sibling()->getLeft()) == Color::BLACK &&
|
||||
NodeColor(n->Sibling()->getRight()) == Color::BLACK)
|
||||
{
|
||||
n->Sibling()->setColor(Color::RED);
|
||||
DeleteCase1(n->getParent());
|
||||
}
|
||||
else
|
||||
DeleteCase4(n);
|
||||
}
|
||||
|
||||
void RBTree::DeleteCase4(PIRBNode n)
|
||||
{
|
||||
if (NodeColor(n->getParent()) == Color::RED &&
|
||||
NodeColor(n->Sibling()) == Color::BLACK &&
|
||||
NodeColor(n->Sibling()->getLeft()) == Color::BLACK &&
|
||||
NodeColor(n->Sibling()->getRight()) == Color::BLACK)
|
||||
{
|
||||
n->Sibling()->setColor(Color::RED);
|
||||
n->getParent()->setColor(Color::BLACK);
|
||||
}
|
||||
else
|
||||
DeleteCase5(n);
|
||||
}
|
||||
|
||||
void RBTree::DeleteCase5(PIRBNode n)
|
||||
{
|
||||
if (n == n->getParent()->getLeft() &&
|
||||
NodeColor(n->Sibling()) == Color::BLACK &&
|
||||
NodeColor(n->Sibling()->getLeft()) == Color::RED &&
|
||||
NodeColor(n->Sibling()->getRight()) == Color::BLACK)
|
||||
{
|
||||
n->Sibling()->setColor(Color::RED);
|
||||
n->Sibling()->getLeft()->setColor(Color::BLACK);
|
||||
RotateRight(n->Sibling());
|
||||
}
|
||||
else if (n == n->getParent()->getRight() &&
|
||||
NodeColor(n->Sibling()) == Color::BLACK &&
|
||||
NodeColor(n->Sibling()->getRight()) == Color::RED &&
|
||||
NodeColor(n->Sibling()->getLeft()) == Color::BLACK)
|
||||
{
|
||||
n->Sibling()->setColor(Color::RED);
|
||||
n->Sibling()->getRight()->setColor(Color::BLACK);
|
||||
RotateLeft(n->Sibling());
|
||||
}
|
||||
|
||||
DeleteCase6(n);
|
||||
}
|
||||
|
||||
void RBTree::DeleteCase6(PIRBNode n)
|
||||
{
|
||||
n->Sibling()->setColor(NodeColor(n->getParent()));
|
||||
n->getParent()->setColor(Color::BLACK);
|
||||
if (n == n->getParent()->getLeft())
|
||||
{
|
||||
//assert nodeColor(n->sibling().right) == Color::RED;
|
||||
n->Sibling()->getRight()->setColor(Color::BLACK);
|
||||
RotateLeft(n->getParent());
|
||||
}
|
||||
else
|
||||
{
|
||||
//assert nodeColor(n->sibling().left) == Color::RED;
|
||||
n->Sibling()->getLeft()->setColor(Color::BLACK);
|
||||
RotateRight(n->getParent());
|
||||
}
|
||||
}
|
||||
|
||||
void RBTree::DoVisitTree(Action<PIRBNode> action, PIRBNode walker)
|
||||
{
|
||||
if (walker->getLeft() != nullptr)
|
||||
{
|
||||
DoVisitTree(action, walker->getLeft());
|
||||
}
|
||||
|
||||
if (action != nullptr)
|
||||
action(walker);
|
||||
|
||||
if (walker->getRight() != nullptr)
|
||||
{
|
||||
DoVisitTree(action, walker->getRight());
|
||||
}
|
||||
}
|
||||
|
||||
void RBTree::VisitTreeNodes(Action<PIRBNode> action)
|
||||
{
|
||||
//IN Order visit
|
||||
PIRBNode walker = getRoot();
|
||||
|
||||
if (walker != nullptr)
|
||||
DoVisitTreeNodes(action, walker);
|
||||
}
|
||||
|
||||
void RBTree::DoVisitTreeNodes(Action<PIRBNode> action, PIRBNode walker)
|
||||
{
|
||||
if (walker->getLeft() != nullptr)
|
||||
{
|
||||
DoVisitTreeNodes(action, walker->getLeft());
|
||||
}
|
||||
|
||||
if (action != nullptr)
|
||||
action(walker);
|
||||
|
||||
if (walker->getRight() != nullptr)
|
||||
{
|
||||
|
||||
DoVisitTreeNodes(action, walker->getRight());
|
||||
}
|
||||
}
|
||||
|
||||
RBTree::iterator::iterator(RBTree &tree, bool end)
|
||||
{
|
||||
Action<PIRBNode> inserter = [&] (PIRBNode pNode)
|
||||
{
|
||||
heap.push_back(pNode);
|
||||
};
|
||||
tree.VisitTreeNodes(inserter);
|
||||
current = end ? heap.end() : heap.begin();
|
||||
}
|
||||
77
Common/cfcpp/RBTree/rbtree.h
Normal file
77
Common/cfcpp/RBTree/rbtree.h
Normal file
@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
#include "irbnode.h"
|
||||
#include <iterator>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
|
||||
namespace RedBlackTree
|
||||
{
|
||||
template <class T>
|
||||
using Action = std::function<void(T)>;
|
||||
|
||||
|
||||
class RBTree
|
||||
{
|
||||
public:
|
||||
RBTree(){}
|
||||
RBTree(PIRBNode root);
|
||||
|
||||
const PIRBNode getRoot() const;
|
||||
void setRoot(const PIRBNode &newRoot);
|
||||
|
||||
bool TryLookup(PIRBNode templ, PIRBNode& val);
|
||||
void Insert(PIRBNode newNode);
|
||||
void Delete(PIRBNode templ, PIRBNode& deletedAlt);
|
||||
|
||||
void VisitTree(Action<PIRBNode> action);
|
||||
void VisitTreeNodes(Action<PIRBNode> action);
|
||||
|
||||
private:
|
||||
static Color NodeColor(PIRBNode n);
|
||||
static PIRBNode MaximumNode(PIRBNode n);
|
||||
|
||||
PIRBNode LookupNode(PIRBNode templ);
|
||||
void ReplaceNode(PIRBNode oldn, PIRBNode newn);
|
||||
void RotateLeft(PIRBNode n);
|
||||
void RotateRight(PIRBNode n);
|
||||
|
||||
void InsertCase1(PIRBNode n);
|
||||
void InsertCase2(PIRBNode n);
|
||||
void InsertCase3(PIRBNode n);
|
||||
void InsertCase4(PIRBNode n);
|
||||
void InsertCase5(PIRBNode n);
|
||||
|
||||
void DeleteCase1(PIRBNode n);
|
||||
void DeleteCase2(PIRBNode n);
|
||||
void DeleteCase3(PIRBNode n);
|
||||
void DeleteCase4(PIRBNode n);
|
||||
void DeleteCase5(PIRBNode n);
|
||||
void DeleteCase6(PIRBNode n);
|
||||
|
||||
void DoVisitTree(Action<PIRBNode> action, PIRBNode walker);
|
||||
void DoVisitTreeNodes(Action<PIRBNode> action, PIRBNode walker);
|
||||
|
||||
public:
|
||||
// todo this from C# and it's weak realization
|
||||
class iterator : public std::iterator<std::bidirectional_iterator_tag, std::ptrdiff_t, IRBNode, IRBNode*, PIRBNode>
|
||||
{
|
||||
std::list<WPIRBNode> heap;
|
||||
std::list<WPIRBNode>::iterator current;
|
||||
public:
|
||||
iterator(RBTree &tree, bool end = false);
|
||||
inline iterator& operator++() {++current; return *this;}
|
||||
inline iterator& operator--() {--current; return *this;}
|
||||
inline bool operator==(const iterator &other) const {return current == other.current;}
|
||||
inline bool operator!=(const iterator &other) const {return current != other.current;}
|
||||
inline PIRBNode operator*() {return current->lock();}
|
||||
inline iterator end() {current = heap.end(); return *this;}
|
||||
};
|
||||
|
||||
iterator&& begin() {return std::move(iterator(*this));}
|
||||
iterator&& end() {return std::move(iterator(*this).end());}
|
||||
|
||||
private:
|
||||
PIRBNode root;
|
||||
};
|
||||
|
||||
}
|
||||
32
Common/cfcpp/RBTree/rbtreeexception.h
Normal file
32
Common/cfcpp/RBTree/rbtreeexception.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
namespace RedBlackTree
|
||||
{
|
||||
class RBTreeException : virtual public std::exception
|
||||
{
|
||||
public:
|
||||
RBTreeException() {}
|
||||
RBTreeException(std::wstring message) : errorMessage(message) {}
|
||||
RBTreeException(std::wstring message, std::exception& ex) : std::exception(ex), errorMessage(message) {}
|
||||
virtual ~RBTreeException() throw () {}
|
||||
|
||||
virtual const wchar_t* what_w() const throw () {
|
||||
return errorMessage.c_str();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::wstring errorMessage;
|
||||
};
|
||||
|
||||
class RBTreeDuplicatedItemException : public RBTreeException
|
||||
{
|
||||
public:
|
||||
RBTreeDuplicatedItemException(std::wstring msg)
|
||||
: RBTreeException(msg)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
52
Common/cfcpp/cfcpp.pro
Normal file
52
Common/cfcpp/cfcpp.pro
Normal file
@ -0,0 +1,52 @@
|
||||
CONFIG -= qt
|
||||
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib
|
||||
|
||||
CONFIG += c++11
|
||||
|
||||
# You can make your code fail to compile if it uses deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
CORE_ROOT_DIR = $$PWD/../..
|
||||
PWD_ROOT_DIR = $$PWD
|
||||
include(../base.pri)
|
||||
|
||||
ADD_DEPENDENCY(UnicodeConverter, kernel)
|
||||
|
||||
SOURCES += \
|
||||
RBTree/rbtree.cpp \
|
||||
cfitem.cpp \
|
||||
cfstorage.cpp \
|
||||
cfstream.cpp \
|
||||
compoundfile.cpp \
|
||||
directoryentry.cpp \
|
||||
header.cpp \
|
||||
sector.cpp \
|
||||
sectorcollection.cpp \
|
||||
stream.cpp \
|
||||
streamrw.cpp \
|
||||
streamview.cpp
|
||||
|
||||
HEADERS += \
|
||||
RBTree/irbnode.h \
|
||||
RBTree/rbtree.h \
|
||||
RBTree/rbtreeexception.h \
|
||||
cfexception.h \
|
||||
cfitem.h \
|
||||
cfstorage.h \
|
||||
cfstream.h \
|
||||
compoundfile.h \
|
||||
directoryentry.h \
|
||||
event.h \
|
||||
guid.h \
|
||||
header.h \
|
||||
idirectoryentry.h \
|
||||
sector.h \
|
||||
sectorcollection.h \
|
||||
slist.h \
|
||||
stream.h \
|
||||
streamrw.h \
|
||||
streamview.h \
|
||||
svector.h
|
||||
80
Common/cfcpp/cfexception.h
Normal file
80
Common/cfcpp/cfexception.h
Normal file
@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
class CFException : virtual public std::exception
|
||||
{
|
||||
public:
|
||||
CFException() {}
|
||||
CFException(std::string message) : errorMessage(message) {}
|
||||
CFException(std::string message, std::exception& ex) : std::exception(ex), errorMessage(message) {}
|
||||
CFException(std::wstring message) : werrorMessage(message) {}
|
||||
CFException(std::wstring message, std::exception& ex) : std::exception(ex), werrorMessage(message) {}
|
||||
|
||||
virtual ~CFException() throw () {}
|
||||
|
||||
virtual const char* what() const throw () {
|
||||
return errorMessage.c_str();
|
||||
}
|
||||
virtual const wchar_t* what_w() const throw () {
|
||||
return werrorMessage.c_str();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string errorMessage;
|
||||
std::wstring werrorMessage;
|
||||
};
|
||||
|
||||
|
||||
class CFFileFormatException : public CFException
|
||||
{
|
||||
public:
|
||||
CFFileFormatException() {}
|
||||
CFFileFormatException(std::string message) : CFException(message) {}
|
||||
virtual ~CFFileFormatException() throw () {}
|
||||
};
|
||||
|
||||
class CFDisposedException : public CFException
|
||||
{
|
||||
public:
|
||||
CFDisposedException() {}
|
||||
CFDisposedException(std::string message) : CFException(message) {}
|
||||
virtual ~CFDisposedException() throw () {}
|
||||
};
|
||||
|
||||
class CFInvalidOperation : public CFException
|
||||
{
|
||||
public:
|
||||
CFInvalidOperation() {}
|
||||
CFInvalidOperation(std::string message) : CFException(message) {}
|
||||
virtual ~CFInvalidOperation() throw () {}
|
||||
};
|
||||
|
||||
class CFCorruptedFileException : public CFException
|
||||
{
|
||||
public:
|
||||
CFCorruptedFileException() {}
|
||||
CFCorruptedFileException(std::string message) : CFException(message) {}
|
||||
virtual ~CFCorruptedFileException() throw () {}
|
||||
};
|
||||
|
||||
class CFDuplicatedItemException : public CFException
|
||||
{
|
||||
public:
|
||||
CFDuplicatedItemException() {}
|
||||
CFDuplicatedItemException(std::wstring message) : CFException(message) {}
|
||||
virtual ~CFDuplicatedItemException() throw () {}
|
||||
};
|
||||
class CFItemNotFound : public CFException
|
||||
{
|
||||
public:
|
||||
CFItemNotFound() {}
|
||||
CFItemNotFound(std::wstring message) : CFException(message) {}
|
||||
virtual ~CFItemNotFound() throw () {}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
101
Common/cfcpp/cfitem.cpp
Normal file
101
Common/cfcpp/cfitem.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
#include "cfitem.h"
|
||||
#include "idirectoryentry.h"
|
||||
#include "compoundfile.h"
|
||||
#include "cfexception.h"
|
||||
|
||||
using namespace CFCPP;
|
||||
|
||||
|
||||
int CFItem::CompareTo(const CFItem &other) const
|
||||
{
|
||||
return dirEntry.lock()->CompareTo(std::dynamic_pointer_cast<RedBlackTree::IRBNode>(other.dirEntry.lock()));
|
||||
}
|
||||
|
||||
bool CFItem::operator==(const CFItem &rightItem) const
|
||||
{
|
||||
return CompareTo(rightItem) == 0;
|
||||
}
|
||||
|
||||
bool CFItem::operator!=(const CFItem &rightItem) const
|
||||
{
|
||||
return CompareTo(rightItem) != 0;
|
||||
}
|
||||
|
||||
int CFItem::GetHashCode() const
|
||||
{
|
||||
return dirEntry.lock()->GetHashCode();
|
||||
}
|
||||
|
||||
std::wstring CFItem::Name() const
|
||||
{
|
||||
auto n = dirEntry.lock()->GetEntryName();
|
||||
if (n.empty() == false)
|
||||
{
|
||||
auto remIter = n.find_last_of('\0');
|
||||
if (remIter != std::wstring::npos)
|
||||
n.erase(remIter);
|
||||
return n;
|
||||
}
|
||||
else
|
||||
return L"";
|
||||
}
|
||||
|
||||
std::streamsize CFItem::size() const
|
||||
{
|
||||
return dirEntry.lock()->getSize();
|
||||
}
|
||||
|
||||
bool CFItem::IsStorage() const
|
||||
{
|
||||
return dirEntry.lock()->getStgType() == StgType::StgStorage;
|
||||
}
|
||||
|
||||
bool CFItem::IsStream() const
|
||||
{
|
||||
return dirEntry.lock()->getStgType() == StgType::StgStream;
|
||||
}
|
||||
|
||||
bool CFItem::ISRoot() const
|
||||
{
|
||||
return dirEntry.lock()->getStgType() == StgType::StgRoot;
|
||||
}
|
||||
|
||||
GUID CFItem::getStorageCLSID() const
|
||||
{
|
||||
return dirEntry.lock()->getStorageCLSID();
|
||||
}
|
||||
|
||||
void CFItem::setStorageCLSID(GUID value)
|
||||
{
|
||||
dirEntry.lock()->setStorageCLSID(value);
|
||||
}
|
||||
|
||||
std::wstring CFItem::ToString() const
|
||||
{
|
||||
if (!dirEntry.expired())
|
||||
return L"[" + std::to_wstring(dirEntry.lock()->getLeftSibling()) + L"," + std::to_wstring(dirEntry.lock()->getSid()) + L"," +
|
||||
std::to_wstring(dirEntry.lock()->getRightSibling()) + L"] " + dirEntry.lock()->GetEntryName();
|
||||
else
|
||||
return L"";
|
||||
}
|
||||
|
||||
void CFItem::setDirEntry(const std::weak_ptr<IDirectoryEntry> &newDirEntry)
|
||||
{
|
||||
if (newDirEntry.expired() || newDirEntry.lock()->getSid() < 0)
|
||||
throw CFException("Attempting to create a CFStorage using an unitialized directory");
|
||||
|
||||
dirEntry = newDirEntry;
|
||||
}
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> CFItem::getDirEntry() const
|
||||
{
|
||||
return dirEntry.lock();
|
||||
}
|
||||
|
||||
|
||||
void CFItem::CheckDisposed() const
|
||||
{
|
||||
if (compoundFile != nullptr && compoundFile->IsClosed())
|
||||
throw CFDisposedException("Owner Compound file has been closed and owned items have been invalidated");
|
||||
}
|
||||
|
||||
53
Common/cfcpp/cfitem.h
Normal file
53
Common/cfcpp/cfitem.h
Normal file
@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include "guid.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
class CompoundFile;
|
||||
class IDirectoryEntry;
|
||||
|
||||
struct DataTime
|
||||
{
|
||||
// TODO
|
||||
char data[8] = {0,0,0,0,0,0,0,0};
|
||||
};
|
||||
|
||||
class CFItem : protected std::enable_shared_from_this<CFItem>
|
||||
{
|
||||
public:
|
||||
int CompareTo(const CFItem& other) const;
|
||||
bool operator==(const CFItem &rightItem) const;
|
||||
bool operator!=(const CFItem &rightItem) const;
|
||||
int GetHashCode() const;
|
||||
std::wstring Name() const;
|
||||
std::streamsize size() const;
|
||||
bool IsStorage() const;
|
||||
bool IsStream() const;
|
||||
bool ISRoot() const;
|
||||
|
||||
inline DataTime getDataTime()const{return DataTime();}
|
||||
inline void setDataTime(const DataTime& value){};
|
||||
|
||||
GUID getStorageCLSID() const;
|
||||
void setStorageCLSID(GUID value);
|
||||
|
||||
int CompareTo(const CFItem& other);
|
||||
std::wstring ToString() const;
|
||||
|
||||
void setDirEntry(const std::weak_ptr<IDirectoryEntry> &newDirEntry);
|
||||
std::shared_ptr<IDirectoryEntry> getDirEntry() const;
|
||||
|
||||
friend class CompoundFile;
|
||||
protected:
|
||||
std::weak_ptr<IDirectoryEntry> dirEntry;
|
||||
CompoundFile* compoundFile;
|
||||
|
||||
protected:
|
||||
CFItem() {};
|
||||
CFItem(CompoundFile* compoundFile) : compoundFile(compoundFile) {}
|
||||
inline CompoundFile* getCompoundFile() {return compoundFile;}
|
||||
void CheckDisposed() const;
|
||||
};
|
||||
}
|
||||
349
Common/cfcpp/cfstorage.cpp
Normal file
349
Common/cfcpp/cfstorage.cpp
Normal file
@ -0,0 +1,349 @@
|
||||
#include "cfstorage.h"
|
||||
#include "cfexception.h"
|
||||
#include "compoundfile.h"
|
||||
#include "directoryentry.h"
|
||||
#include "RBTree/rbtreeexception.h"
|
||||
#include "RBTree/irbnode.h"
|
||||
|
||||
using namespace CFCPP;
|
||||
using RedBlackTree::RBTree;
|
||||
|
||||
|
||||
CFStorage::CFStorage(CompoundFile *compFile, const std::weak_ptr<IDirectoryEntry> &dirEntry) :
|
||||
CFItem(compFile)
|
||||
{
|
||||
setDirEntry(dirEntry);
|
||||
}
|
||||
|
||||
std::shared_ptr<RBTree> CFStorage::getChildren()
|
||||
{
|
||||
if (children == nullptr)
|
||||
{
|
||||
children = LoadChildren(this->dirEntry.lock()->getSid());
|
||||
|
||||
if (children == nullptr)
|
||||
{
|
||||
children = CompoundFile::CreateNewTree();
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
std::shared_ptr<CFStream> CFStorage::AddStream(const std::wstring& streamName)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (streamName.empty())
|
||||
throw CFException("Stream name cannot be null or empty");
|
||||
|
||||
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> dirEntry = DirectoryEntry::TryNew(streamName, StgType::StgStream, compoundFile->GetDirectories());
|
||||
|
||||
try
|
||||
{
|
||||
// Add object to Siblings tree
|
||||
children->Insert(dirEntry);
|
||||
|
||||
//... and set the root of the tree as new child of the current item directory entry
|
||||
dirEntry->setChild(std::dynamic_pointer_cast<IDirectoryEntry>(children->getRoot())->getSid());
|
||||
}
|
||||
catch (RedBlackTree::RBTreeException &rbex)
|
||||
{
|
||||
compoundFile->ResetDirectoryEntry(dirEntry->getSid());
|
||||
|
||||
throw CFDuplicatedItemException(L"An entry with name '" + streamName + L"' is already present in storage '" + Name() + L"' ");
|
||||
}
|
||||
|
||||
return std::shared_ptr<CFStream>(new CFStream(compoundFile, dirEntry));
|
||||
}
|
||||
|
||||
std::shared_ptr<CFStream> CFStorage::GetStream(const std::wstring& streamName)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> tmp = DirectoryEntry::Mock(streamName, StgType::StgStream);
|
||||
|
||||
RedBlackTree::PIRBNode outDe;
|
||||
|
||||
if (children->TryLookup(tmp, outDe) &&
|
||||
((std::static_pointer_cast<IDirectoryEntry>(outDe))->getStgType() == StgType::StgStream))
|
||||
{
|
||||
return std::shared_ptr<CFStream>(new CFStream(compoundFile, std::static_pointer_cast<IDirectoryEntry>(outDe)));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CFItemNotFound(L"Cannot find item [" + streamName + L"] within the current storage");
|
||||
}
|
||||
}
|
||||
|
||||
bool CFStorage::TryGetStream(const std::wstring& streamName, std::shared_ptr<CFStream>& cfStream)
|
||||
{
|
||||
bool result = false;
|
||||
cfStream.reset();
|
||||
|
||||
try
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> tmp = DirectoryEntry::Mock(streamName, StgType::StgStream);
|
||||
|
||||
RedBlackTree::PIRBNode outDe;
|
||||
|
||||
if (children->TryLookup(tmp, outDe) &&
|
||||
((std::static_pointer_cast<IDirectoryEntry>(outDe))->getStgType() == StgType::StgStream))
|
||||
{
|
||||
cfStream = std::shared_ptr<CFStream>(new CFStream(compoundFile, std::static_pointer_cast<IDirectoryEntry>(outDe)));
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
catch (CFDisposedException& ex)
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<CFStorage> CFStorage::GetStorage(const std::wstring &storageName)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> templ = DirectoryEntry::Mock(storageName, StgType::StgInvalid);
|
||||
RedBlackTree::PIRBNode outDe;
|
||||
|
||||
if (children->TryLookup(templ, outDe) && std::static_pointer_cast<IDirectoryEntry>(outDe)->getStgType() == StgType::StgStorage)
|
||||
{
|
||||
return std::shared_ptr<CFStorage>(new CFStorage(compoundFile, std::dynamic_pointer_cast<IDirectoryEntry>(outDe)));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CFItemNotFound(L"Cannot find item [" + storageName + L"] within the current storage");
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<CFStorage> CFStorage::TryGetStorage(const std::wstring &storageName)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> templ = DirectoryEntry::Mock(storageName, StgType::StgInvalid);
|
||||
RedBlackTree::PIRBNode outDe;
|
||||
|
||||
if (children->TryLookup(templ, outDe) && std::static_pointer_cast<IDirectoryEntry>(outDe)->getStgType() == StgType::StgStorage)
|
||||
{
|
||||
return std::shared_ptr<CFStorage>(new CFStorage(compoundFile, std::dynamic_pointer_cast<IDirectoryEntry>(outDe)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool CFStorage::TryGetStorage(const std::wstring &storageName, std::shared_ptr<CFStorage>& cfStorage)
|
||||
{
|
||||
bool result = false;
|
||||
cfStorage.reset();
|
||||
|
||||
try
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> templ = DirectoryEntry::Mock(storageName, StgType::StgInvalid);
|
||||
RedBlackTree::PIRBNode outDe;
|
||||
|
||||
if (children->TryLookup(templ, outDe) && std::static_pointer_cast<IDirectoryEntry>(outDe)->getStgType() == StgType::StgStorage)
|
||||
{
|
||||
cfStorage.reset(new CFStorage(compoundFile, std::dynamic_pointer_cast<IDirectoryEntry>(outDe)));
|
||||
result = true;
|
||||
}
|
||||
|
||||
}
|
||||
catch (CFDisposedException& ex)
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<CFStorage> CFStorage::AddStorage(const std::wstring &storageName)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (storageName.empty())
|
||||
throw CFException("Stream name cannot be null or empty");
|
||||
|
||||
// Add new Storage directory entry
|
||||
std::shared_ptr<IDirectoryEntry> cfo
|
||||
= DirectoryEntry::New(storageName, StgType::StgStorage, compoundFile->GetDirectories());
|
||||
|
||||
//this.CompoundFile.InsertNewDirectoryEntry(cfo);
|
||||
|
||||
try
|
||||
{
|
||||
// Add object to Siblings tree
|
||||
children->Insert(cfo);
|
||||
}
|
||||
catch (RedBlackTree::RBTreeDuplicatedItemException& ex)
|
||||
{
|
||||
compoundFile->ResetDirectoryEntry(cfo->getSid());
|
||||
cfo.reset();
|
||||
throw CFDuplicatedItemException(L"An entry with name '" + storageName + L"' is already present in storage '" + Name() + L"' ");
|
||||
}
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> childrenRoot = std::dynamic_pointer_cast<IDirectoryEntry>(children->getRoot());
|
||||
dirEntry.lock()->setChild(childrenRoot->getSid());
|
||||
|
||||
return std::shared_ptr<CFStorage>(new CFStorage(compoundFile, cfo));
|
||||
}
|
||||
|
||||
void CFStorage::VisitEntries(RedBlackTree::Action<std::shared_ptr<CFItem> > action, bool recursive)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (action != nullptr)
|
||||
{
|
||||
SVector<RedBlackTree::IRBNode> subStorages;
|
||||
|
||||
RedBlackTree::Action<RedBlackTree::PIRBNode> internalAction =
|
||||
[&] (RedBlackTree::PIRBNode targetNode)
|
||||
{
|
||||
auto d = std::dynamic_pointer_cast<IDirectoryEntry>(targetNode);
|
||||
if (d->getStgType() == StgType::StgStream)
|
||||
{
|
||||
std::shared_ptr<CFStream> pstream (new CFStream(compoundFile, d));
|
||||
action(std::static_pointer_cast<CFItem>(pstream));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::shared_ptr<CFStorage> pstorage(new CFStorage(compoundFile, d));
|
||||
action(std::static_pointer_cast<CFItem>(pstorage));
|
||||
}
|
||||
|
||||
if (d->getChild() != DirectoryEntry::NOSTREAM)
|
||||
subStorages.push_back(targetNode);
|
||||
};
|
||||
|
||||
children->VisitTreeNodes(internalAction);
|
||||
|
||||
if (recursive && subStorages.size() > 0)
|
||||
for (const auto& n : subStorages)
|
||||
{
|
||||
auto d = std::dynamic_pointer_cast<IDirectoryEntry>(n);
|
||||
CFStorage(compoundFile, d).VisitEntries(action, recursive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CFStorage::Delete(const std::wstring &entryName)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
// Find entry to delete
|
||||
auto tmp = DirectoryEntry::Mock(entryName, StgType::StgInvalid);
|
||||
|
||||
RedBlackTree::PIRBNode foundObj;
|
||||
|
||||
children->TryLookup(tmp, foundObj);
|
||||
|
||||
if (foundObj == nullptr)
|
||||
throw CFItemNotFound(L"Entry named [" + entryName + L"] was not found");
|
||||
|
||||
if (std::dynamic_pointer_cast<IDirectoryEntry>(foundObj)->getStgType() == StgType::StgRoot)
|
||||
throw CFException("Root storage cannot be removed");
|
||||
|
||||
|
||||
RedBlackTree::PIRBNode altDel;
|
||||
switch (std::dynamic_pointer_cast<IDirectoryEntry>(foundObj)->getStgType())
|
||||
{
|
||||
case StgType::StgStorage:
|
||||
{
|
||||
std::shared_ptr<CFStorage> temp(new CFStorage(compoundFile, std::dynamic_pointer_cast<IDirectoryEntry>(foundObj)));
|
||||
|
||||
// This is a storage. we have to remove children items first
|
||||
for (const auto& de : *(temp->getChildren()))
|
||||
{
|
||||
auto ded = std::dynamic_pointer_cast<IDirectoryEntry>(de);
|
||||
temp->Delete(ded->GetEntryName());
|
||||
}
|
||||
|
||||
// ...then we need to rethread the root of siblings tree...
|
||||
if (children->getRoot() != nullptr)
|
||||
dirEntry.lock()->setChild(std::dynamic_pointer_cast<IDirectoryEntry>(children->getRoot())->getSid());
|
||||
else
|
||||
dirEntry.lock()->setChild(DirectoryEntry::NOSTREAM);
|
||||
|
||||
// ...and finally Remove storage item from children tree...
|
||||
children->Delete(foundObj, altDel);
|
||||
|
||||
// ...and remove directory (storage) entry
|
||||
|
||||
if (altDel != nullptr)
|
||||
{
|
||||
foundObj = altDel;
|
||||
}
|
||||
|
||||
compoundFile->InvalidateDirectoryEntry(std::dynamic_pointer_cast<IDirectoryEntry>(foundObj)->getSid());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case StgType::StgStream:
|
||||
{
|
||||
// Free directory associated data stream.
|
||||
compoundFile->FreeAssociatedData(std::dynamic_pointer_cast<IDirectoryEntry>(foundObj)->getSid());
|
||||
|
||||
// Remove item from children tree
|
||||
children->Delete(foundObj, altDel);
|
||||
|
||||
// Rethread the root of siblings tree...
|
||||
if (children->getRoot() != nullptr)
|
||||
dirEntry.lock()->setChild(std::dynamic_pointer_cast<IDirectoryEntry>(children->getRoot())->getSid());
|
||||
else
|
||||
dirEntry.lock()->setChild(DirectoryEntry::NOSTREAM);
|
||||
|
||||
// Delete operation could possibly have cloned a directory, changing its SID.
|
||||
// Invalidate the ACTUALLY deleted directory.
|
||||
if (altDel != nullptr)
|
||||
{
|
||||
foundObj = altDel;
|
||||
}
|
||||
|
||||
compoundFile->InvalidateDirectoryEntry(std::dynamic_pointer_cast<IDirectoryEntry>(foundObj)->getSid());
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CFStorage::RenameItem(const std::wstring &oldItemName, const std::wstring &newItemName)
|
||||
{
|
||||
auto templ = DirectoryEntry::Mock(oldItemName, StgType::StgInvalid);
|
||||
RedBlackTree::PIRBNode item;
|
||||
if (children->TryLookup(templ, item))
|
||||
{
|
||||
std::dynamic_pointer_cast<DirectoryEntry>(item)->SetEntryName(newItemName);
|
||||
}
|
||||
else throw CFItemNotFound(L"Item " + oldItemName + L" not found in Storage");
|
||||
|
||||
children.reset();
|
||||
children = LoadChildren(dirEntry.lock()->getSid()); //Rethread
|
||||
|
||||
if (children == nullptr)
|
||||
{
|
||||
children = compoundFile->CreateNewTree();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<RBTree> CFStorage::LoadChildren(int SID)
|
||||
{
|
||||
std::shared_ptr<RBTree> childrenTree = compoundFile->GetChildrenTree(SID);
|
||||
|
||||
if (childrenTree->getRoot() != nullptr)
|
||||
dirEntry.lock()->setChild((std::static_pointer_cast<IDirectoryEntry>(childrenTree->getRoot())->getSid()));
|
||||
else
|
||||
dirEntry.lock()->setChild(DirectoryEntry::NOSTREAM);
|
||||
|
||||
return childrenTree;
|
||||
}
|
||||
36
Common/cfcpp/cfstorage.h
Normal file
36
Common/cfcpp/cfstorage.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "RBTree/rbtree.h"
|
||||
#include "idirectoryentry.h"
|
||||
#include "cfstream.h"
|
||||
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
class CFStorage : public CFItem
|
||||
{
|
||||
public:
|
||||
CFStorage(CompoundFile* compFile, const std::weak_ptr<IDirectoryEntry> &dirEntry);
|
||||
|
||||
|
||||
std::shared_ptr<RedBlackTree::RBTree> getChildren();
|
||||
std::shared_ptr<CFStream> AddStream(const std::wstring& streamName);
|
||||
std::shared_ptr<CFStream> GetStream(const std::wstring& streamName);
|
||||
|
||||
bool TryGetStream(const std::wstring& streamName, std::shared_ptr<CFStream> &cfStream);
|
||||
std::shared_ptr<CFStorage> GetStorage(const std::wstring& storageName);
|
||||
std::shared_ptr<CFStorage> TryGetStorage(const std::wstring& storageName);
|
||||
bool TryGetStorage(const std::wstring& storageName, std::shared_ptr<CFStorage> &cfStorage);
|
||||
std::shared_ptr<CFStorage> AddStorage(const std::wstring& storageName);
|
||||
void VisitEntries(RedBlackTree::Action<std::shared_ptr<CFItem>> action, bool recursive);
|
||||
void Delete(const std::wstring& entryName);
|
||||
void RenameItem(const std::wstring& oldItemName, const std::wstring& newItemName);
|
||||
std::streamsize size() const {return CFItem::size();}
|
||||
|
||||
private:
|
||||
std::shared_ptr<RedBlackTree::RBTree> LoadChildren(int SID);
|
||||
|
||||
private:
|
||||
std::shared_ptr<RedBlackTree::RBTree> children;
|
||||
};
|
||||
}
|
||||
86
Common/cfcpp/cfstream.cpp
Normal file
86
Common/cfcpp/cfstream.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
#include "cfstream.h"
|
||||
#include "cfexception.h"
|
||||
#include "idirectoryentry.h"
|
||||
#include "compoundfile.h"
|
||||
|
||||
using namespace CFCPP;
|
||||
|
||||
CFStream::CFStream(CompoundFile* compFile, std::weak_ptr<IDirectoryEntry> dirEntry) :
|
||||
CFItem(compFile)
|
||||
{
|
||||
if (dirEntry.expired() || dirEntry.lock()->getSid() < 0)
|
||||
throw CFException("Attempting to create a CFStorage using an unitialized directory");
|
||||
|
||||
this->dirEntry = dirEntry;
|
||||
}
|
||||
|
||||
void CFStream::SetData(const std::vector<BYTE> &data)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
compoundFile->FreeData(this);
|
||||
compoundFile->WriteData(shared_from_this(), data);
|
||||
}
|
||||
|
||||
void CFStream::Write(const std::vector<BYTE> &data, std::streamsize position)
|
||||
{
|
||||
Write(data, position, 0, data.size());
|
||||
}
|
||||
|
||||
void CFStream::Write(const std::vector<BYTE> &data, std::streamsize position, int offset, int count)
|
||||
{
|
||||
CheckDisposed();
|
||||
compoundFile->WriteData(shared_from_this(), data, position, offset, count);
|
||||
}
|
||||
|
||||
void CFStream::Append(const std::vector<BYTE> &data)
|
||||
{
|
||||
CheckDisposed();
|
||||
if (size() > 0)
|
||||
{
|
||||
compoundFile->AppendData(shared_from_this(), data);
|
||||
}
|
||||
else
|
||||
{
|
||||
compoundFile->WriteData(shared_from_this(), data);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<BYTE> CFStream::getData() const
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
return compoundFile->GetData(this);
|
||||
}
|
||||
|
||||
int CFStream::Read(std::vector<BYTE> &buffer, std::streamsize position, int count)
|
||||
{
|
||||
CheckDisposed();
|
||||
return compoundFile->ReadData(this, position, buffer, 0, count);
|
||||
}
|
||||
|
||||
int CFStream::Read(std::vector<BYTE> &buffer, std::streamsize position, int offset, int count)
|
||||
{
|
||||
CheckDisposed();
|
||||
return compoundFile->ReadData(this, position, buffer, offset, count);
|
||||
}
|
||||
|
||||
void CFStream::CopyFrom(const Stream &input)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
std::vector<BYTE> buffer(Length(input));
|
||||
|
||||
// if (input.CanSeek)
|
||||
{
|
||||
input->seek(0, std::ios::beg);
|
||||
}
|
||||
|
||||
input->read(reinterpret_cast<char*>(buffer.data()), Length(input));
|
||||
SetData(buffer);
|
||||
}
|
||||
|
||||
void CFStream::Resize(std::streamsize length)
|
||||
{
|
||||
compoundFile->SetStreamLength(shared_from_this(), length);
|
||||
}
|
||||
25
Common/cfcpp/cfstream.h
Normal file
25
Common/cfcpp/cfstream.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "stream.h"
|
||||
#include "cfitem.h"
|
||||
#include <vector>
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
class CFStream : public CFItem
|
||||
{
|
||||
public:
|
||||
CFStream(CompoundFile* compFile, std::weak_ptr<IDirectoryEntry> dirEntry);
|
||||
|
||||
void SetData(const std::vector<BYTE>& data);
|
||||
void Write(const std::vector<BYTE>& data, std::streamsize position);
|
||||
void Write(const std::vector<BYTE>& data, std::streamsize position, int offset, int count);
|
||||
void Append(const std::vector<BYTE>& data);
|
||||
std::vector<BYTE> getData() const;
|
||||
int Read(std::vector<BYTE>& buffer, std::streamsize position, int count);
|
||||
int Read(std::vector<BYTE>& buffer, std::streamsize position, int offset, int count);
|
||||
void CopyFrom(const Stream& input);
|
||||
void Resize(std::streamsize length);
|
||||
std::streamsize size() const {return CFItem::size();}
|
||||
};
|
||||
}
|
||||
1910
Common/cfcpp/compoundfile.cpp
Normal file
1910
Common/cfcpp/compoundfile.cpp
Normal file
File diff suppressed because it is too large
Load Diff
156
Common/cfcpp/compoundfile.h
Normal file
156
Common/cfcpp/compoundfile.h
Normal file
@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
#include "sectorcollection.h"
|
||||
#include "cfstorage.h"
|
||||
#include "slist.h"
|
||||
#include <unordered_set>
|
||||
#include "RBTree/rbtree.h"
|
||||
#include "idirectoryentry.h"
|
||||
#include <mutex>
|
||||
#include "header.h"
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
class DirectoryEntry;
|
||||
|
||||
enum CFSConfiguration
|
||||
{
|
||||
|
||||
Default = 1,
|
||||
SectorRecycle = 2,
|
||||
EraseFreeSectors = 4,
|
||||
NoValidationException = 8,
|
||||
LeaveOpen = 16
|
||||
};
|
||||
|
||||
enum CFSUpdateMode
|
||||
{
|
||||
/// ReadOnly update mode prevents overwriting
|
||||
/// of the opened file.
|
||||
/// Data changes are allowed but they have to be
|
||||
/// persisted on a different file when required
|
||||
ReadOnly,
|
||||
|
||||
/// Update mode allows subsequent data changing operations
|
||||
/// to be persisted directly on the opened file or stream
|
||||
/// method when required. Warning: this option may cause existing data loss if misused.
|
||||
Update
|
||||
};
|
||||
|
||||
class CompoundFile
|
||||
{
|
||||
public:
|
||||
CompoundFile(const std::wstring &fileName, CFSUpdateMode updateMode, CFSConfiguration configParameters);
|
||||
CompoundFile(CFSVersion cfsVersion, CFSConfiguration configFlags);
|
||||
CompoundFile(const std::wstring &fileName);
|
||||
CompoundFile(Stream stream);
|
||||
CompoundFile();
|
||||
void Commit(bool releaseMemory = false);
|
||||
inline bool HasSourceStream() {return sourceStream != nullptr;}
|
||||
|
||||
void Close();
|
||||
|
||||
static std::shared_ptr<RedBlackTree::RBTree> CreateNewTree();
|
||||
std::shared_ptr<RedBlackTree::RBTree> GetChildrenTree(int sid);
|
||||
bool IsClosed()const;
|
||||
SVector<IDirectoryEntry> &GetDirectories();
|
||||
void ResetDirectoryEntry(int sid);
|
||||
void InvalidateDirectoryEntry(int sid);
|
||||
void FreeAssociatedData(int sid);
|
||||
void FreeData(CFStream* stream);
|
||||
void WriteData(std::shared_ptr<CFItem> cfItem, const std::vector<BYTE>& buffer, std::streamsize position, int offset, int count);
|
||||
void WriteData(std::shared_ptr<CFItem> cfItem, std::streamsize position, const std::vector<BYTE>& buffer);
|
||||
void WriteData(std::shared_ptr<CFItem> cfItem, const std::vector<BYTE>& buffer);
|
||||
void AppendData(std::shared_ptr<CFItem> cfItem, const std::vector<BYTE>& buffer);
|
||||
void SetStreamLength(std::shared_ptr<CFItem> cfItem, std::streamsize length);
|
||||
SList<Sector> FindFreeSectors(SectorType sType);
|
||||
std::vector<BYTE> GetData(const CFStream *cFStream);
|
||||
int ReadData(CFStream* cFStream, std::streamsize position, std::vector<BYTE>& buffer, int count);
|
||||
int ReadData(CFStream* cFStream, std::streamsize position, std::vector<BYTE>& buffer, int offset, int count);
|
||||
|
||||
std::vector<BYTE> GetDataBySID(int sid);
|
||||
GUID getGuidBySID(int sid);
|
||||
GUID getGuidForStream(int sid);
|
||||
|
||||
void Save(std::wstring wFileName);
|
||||
void Save(Stream stream);
|
||||
|
||||
protected:
|
||||
int GetSectorSize();
|
||||
void Dispose(bool disposing);
|
||||
|
||||
private:
|
||||
void CheckForLockSector();
|
||||
void OnSizeLimitReached();
|
||||
void LoadFile(std::wstring fileName);
|
||||
void SetFileName(std::wstring fileName);
|
||||
void LoadStream(Stream stream);
|
||||
void Load(Stream stream);
|
||||
|
||||
SVector<Sector> GetFatSectorChain();
|
||||
SVector<Sector> GetDifatSectorChain();
|
||||
SVector<Sector> GetNormalSectorChain(int secID);
|
||||
SVector<Sector> GetMiniSectorChain(int secID);
|
||||
SVector<Sector> GetSectorChain(int secID, SectorType chainType);
|
||||
void EnsureUniqueSectorIndex(int nextSecID, std::unordered_set<int> &processedSectors);
|
||||
void CommitDirectory();
|
||||
void Close(bool closeStream);
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> RootEntry();
|
||||
SVector<IDirectoryEntry> FindDirectoryEntries(std::wstring entryName);
|
||||
|
||||
std::shared_ptr<RedBlackTree::RBTree> DoLoadChildrenTrusted(std::shared_ptr<IDirectoryEntry> de);
|
||||
void DoLoadChildren(std::shared_ptr<RedBlackTree::RBTree> bst, std::shared_ptr<IDirectoryEntry> de);
|
||||
void NullifyChildNodes(std::shared_ptr<IDirectoryEntry> de);
|
||||
void LoadSiblings(std::shared_ptr<RedBlackTree::RBTree> bst, std::shared_ptr<IDirectoryEntry> de);
|
||||
void DoLoadSiblings(std::shared_ptr<RedBlackTree::RBTree> bst, std::shared_ptr<IDirectoryEntry> de);
|
||||
bool ValidateSibling(int sid);
|
||||
void LoadDirectories();
|
||||
// TODO
|
||||
void FreeMiniChain(SVector<Sector>& sectorChain, bool zeroSector);
|
||||
void FreeMiniChain(SVector<Sector>& sectorChain, int nth_sector_to_remove, bool zeroSector);
|
||||
void FreeChain(SVector<Sector>& sectorChain, int nth_sector_to_remove, bool zeroSector);
|
||||
void FreeChain(SVector<Sector>& sectorChain, bool zeroSector);
|
||||
|
||||
void AllocateSectorChain(SVector<Sector>& sectorChain);
|
||||
void AllocateFATSectorChain(SVector<Sector>& sectorChain);
|
||||
void AllocateDIFATSectorChain(SVector<Sector>& FATsectorChain);
|
||||
void AllocateMiniSectorChain(SVector<Sector>& sectorChain);
|
||||
void PersistMiniStreamToStream(const SVector<Sector>& miniSectorChain);
|
||||
static int LowSaturation(int i);
|
||||
void SetSectorChain(SVector<Sector> sectorChain);
|
||||
|
||||
CFSVersion getVersion() const;
|
||||
|
||||
public:
|
||||
CFSConfiguration configuration = Default;
|
||||
std::unique_ptr<Header> header;
|
||||
Stream sourceStream;
|
||||
|
||||
private:
|
||||
const int HEADER_DIFAT_ENTRIES_COUNT = 109;
|
||||
int DIFAT_SECTOR_FAT_ENTRIES_COUNT = 127;
|
||||
int FAT_SECTOR_ENTRIES_COUNT = 128;
|
||||
const int SIZE_OF_SID = 4;
|
||||
bool sectorRecycle = false;
|
||||
bool eraseFreeSectors = false;
|
||||
static constexpr int FLUSHING_QUEUE_SIZE = 6000;
|
||||
static constexpr int FLUSHING_BUFFER_MAX_SIZE = 1024 * 1024 * 16;
|
||||
SectorCollection sectors;
|
||||
std::fstream stream;
|
||||
std::string fileName;
|
||||
std::shared_ptr<CFStorage> rootStorage;
|
||||
|
||||
bool closeStream = true;
|
||||
bool _transactionLockAdded = false;
|
||||
int _lockSectorId = -1;
|
||||
bool _transactionLockAllocated = false;
|
||||
bool validationExceptionEnabled = true;
|
||||
bool _disposed;//false
|
||||
CFSUpdateMode updateMode;
|
||||
SVector<IDirectoryEntry> directoryEntries;
|
||||
std::list<int> levelSIDs;
|
||||
std::mutex lockObject;
|
||||
|
||||
};
|
||||
}
|
||||
319
Common/cfcpp/directoryentry.cpp
Normal file
319
Common/cfcpp/directoryentry.cpp
Normal file
@ -0,0 +1,319 @@
|
||||
#include "directoryentry.h"
|
||||
#include "cfexception.h"
|
||||
#include "streamrw.h"
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
using namespace CFCPP;
|
||||
|
||||
|
||||
DirectoryEntry::DirectoryEntry(std::wstring name, StgType stgType, SVector<IDirectoryEntry> dirRepository)
|
||||
{
|
||||
this->dirRepository = dirRepository;
|
||||
|
||||
this->stgType = stgType;
|
||||
|
||||
if (stgType == StgType::StgStorage)
|
||||
{
|
||||
// creationDate = BitConverter.GetBytes((DateTime.Now.ToFileTime()));
|
||||
startSetc = ZERO;
|
||||
}
|
||||
|
||||
if (stgType == StgType::StgInvalid)
|
||||
{
|
||||
startSetc = ZERO;
|
||||
}
|
||||
|
||||
if (name.size())
|
||||
{
|
||||
DirectoryEntry::SetEntryName(name);
|
||||
}
|
||||
}
|
||||
|
||||
int DirectoryEntry::getSid() const
|
||||
{
|
||||
return sid;
|
||||
}
|
||||
|
||||
void DirectoryEntry::setSid(int newSid)
|
||||
{
|
||||
sid = newSid;
|
||||
}
|
||||
|
||||
std::wstring DirectoryEntry::GetEntryName() const
|
||||
{
|
||||
if (entryName[0] != '\0' && nameLength > 0)
|
||||
{
|
||||
wchar_t name[32];
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
name[i] = entryName[2*i] + (entryName[2*i+1] << 8);
|
||||
}
|
||||
return std::wstring (name, name + nameLength/2 - 1);
|
||||
|
||||
}
|
||||
else
|
||||
return L"";
|
||||
}
|
||||
|
||||
void DirectoryEntry::SetEntryName(const std::wstring &entryName)
|
||||
{
|
||||
if (entryName.empty())
|
||||
{
|
||||
std::fill(this->entryName, this->entryName+64, '\0');
|
||||
this->nameLength = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (
|
||||
entryName.find(L"\\") == std::wstring::npos ||
|
||||
entryName.find(L"/") == std::wstring::npos ||
|
||||
entryName.find(L":") == std::wstring::npos ||
|
||||
entryName.find(L"!") == std::wstring::npos
|
||||
) throw CFException("Invalid character in entry: the characters '\\', '/', ':','!' cannot be used in entry name");
|
||||
|
||||
if (entryName.length() > 31)
|
||||
throw CFException("Entry name MUST NOT exceed 31 characters");
|
||||
|
||||
|
||||
std::copy(entryName.data(), entryName.data() + entryName.length(), this->entryName);
|
||||
reinterpret_cast<wchar_t*>(this->entryName)[entryName.length()] = L'\0';
|
||||
|
||||
this->nameLength = (ushort)entryName.size() + 2;
|
||||
}
|
||||
}
|
||||
|
||||
int DirectoryEntry::GetHashCode() const
|
||||
{
|
||||
return (int)fnv_hash(entryName, nameLength);
|
||||
}
|
||||
|
||||
void DirectoryEntry::Write(Stream stream) const
|
||||
{
|
||||
StreamRW rw(stream);
|
||||
rw.WriteArray(entryName, sizeof (entryName));
|
||||
rw.Write(nameLength);
|
||||
rw.Write((BYTE)stgType);
|
||||
rw.Write((BYTE)stgColor);
|
||||
rw.Write(leftSibling);
|
||||
rw.Write(rightSibling);
|
||||
rw.Write(child);
|
||||
rw.WriteArray(reinterpret_cast<const char*>(&storageCLSID), sizeof (storageCLSID));
|
||||
rw.Write(stateBits);
|
||||
rw.WriteArray(reinterpret_cast<const char*>(&creationDate), sizeof (creationDate));
|
||||
rw.WriteArray(reinterpret_cast<const char*>(&modifyDate), sizeof (modifyDate));
|
||||
rw.Write(startSetc);
|
||||
rw.Write(size);
|
||||
|
||||
rw.Close();
|
||||
}
|
||||
|
||||
void DirectoryEntry::Read(Stream stream, CFSVersion ver)
|
||||
{
|
||||
StreamRW rw(stream);
|
||||
|
||||
rw.ReadArray(entryName, 64);
|
||||
nameLength = rw.Read<decltype(nameLength)>();
|
||||
stgType = (StgType)rw.Read<BYTE>();
|
||||
stgColor = (StgColor)rw.Read<BYTE>();
|
||||
leftSibling = rw.Read<decltype(leftSibling)>();
|
||||
rightSibling = rw.Read<decltype(rightSibling)>();
|
||||
child = rw.Read<decltype(child)>();
|
||||
|
||||
// Thanks to bugaccount (BugTrack id 3519554)
|
||||
if (stgType == StgType::StgInvalid)
|
||||
{
|
||||
leftSibling = NOSTREAM;
|
||||
rightSibling = NOSTREAM;
|
||||
child = NOSTREAM;
|
||||
}
|
||||
|
||||
rw.ReadArray(reinterpret_cast<char*>(&storageCLSID), 16);
|
||||
stateBits = rw.Read<decltype (stateBits)>();
|
||||
rw.ReadArray(reinterpret_cast<char*>(&creationDate), 8);
|
||||
rw.ReadArray(reinterpret_cast<char*>(&modifyDate), 8);
|
||||
startSetc = rw.Read<decltype (startSetc)>();
|
||||
|
||||
if (ver == CFSVersion::Ver_3)
|
||||
{
|
||||
// avoid dirty read for version 3 files (max size: 32bit integer)
|
||||
// where most significant bits are not initialized to zero
|
||||
|
||||
size = rw.Read<int>();
|
||||
rw.Read<INT>(); //discard most significant 4 (possibly) dirty bytes
|
||||
}
|
||||
else
|
||||
{
|
||||
size = rw.Read<decltype (size)>();
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring DirectoryEntry::ToString() const
|
||||
{
|
||||
std::wstringstream wss;
|
||||
wss << Name() << L" [" << sid << L"]" << (stgType == StgType::StgStream ? L"Stream" : L"Storage");
|
||||
return wss.str();
|
||||
}
|
||||
|
||||
RedBlackTree::PIRBNode DirectoryEntry::getLeft() const
|
||||
{
|
||||
if (leftSibling == NOSTREAM)
|
||||
return {};
|
||||
|
||||
return dirRepository[leftSibling];
|
||||
}
|
||||
|
||||
RedBlackTree::PIRBNode DirectoryEntry::getRight() const
|
||||
{
|
||||
if (rightSibling == DirectoryEntry::NOSTREAM)
|
||||
return {};
|
||||
|
||||
return dirRepository[rightSibling];
|
||||
}
|
||||
|
||||
void DirectoryEntry::setLeft(RedBlackTree::PIRBNode pNode)
|
||||
{
|
||||
leftSibling = pNode != nullptr ? static_cast<IDirectoryEntry*>(pNode.get())->getSid() : DirectoryEntry::NOSTREAM;
|
||||
|
||||
if (leftSibling != DirectoryEntry::NOSTREAM)
|
||||
dirRepository[leftSibling]->setParent(shared_from_this());
|
||||
}
|
||||
|
||||
void DirectoryEntry::setRight(RedBlackTree::PIRBNode pNode)
|
||||
{
|
||||
rightSibling = pNode != nullptr ? static_cast<IDirectoryEntry*>(pNode.get())->getSid() : DirectoryEntry::NOSTREAM;
|
||||
|
||||
if (rightSibling != DirectoryEntry::NOSTREAM)
|
||||
dirRepository[rightSibling]->setParent(shared_from_this());
|
||||
}
|
||||
|
||||
RedBlackTree::PIRBNode DirectoryEntry::Sibling() const
|
||||
{
|
||||
if (shared_from_this() == getParent()->getLeft())
|
||||
return getParent()->getRight();
|
||||
else
|
||||
return getParent()->getLeft();
|
||||
}
|
||||
|
||||
RedBlackTree::PIRBNode DirectoryEntry::Uncle() const
|
||||
{
|
||||
return parent.use_count() != 0 ? getParent()->Sibling() : RedBlackTree::PIRBNode();
|
||||
}
|
||||
|
||||
void DirectoryEntry::AssignValueTo(RedBlackTree::PIRBNode other)
|
||||
{
|
||||
auto d = std::dynamic_pointer_cast<DirectoryEntry>(other); // as
|
||||
if (d == nullptr)
|
||||
return;
|
||||
|
||||
|
||||
d->SetEntryName(this->GetEntryName());
|
||||
|
||||
d->creationDate = creationDate;
|
||||
d->modifyDate = modifyDate;
|
||||
|
||||
d->size = this->size;
|
||||
d->startSetc = this->startSetc;
|
||||
d->stateBits = this->stateBits;
|
||||
d->stgType = this->stgType;
|
||||
d->storageCLSID = this->storageCLSID;
|
||||
d->child = this->child;
|
||||
}
|
||||
|
||||
int DirectoryEntry::CompareTo(const RedBlackTree::PIRBNode &other) const
|
||||
{
|
||||
IDirectoryEntry* otherDir = dynamic_cast<IDirectoryEntry*>(other.get());
|
||||
|
||||
if (otherDir == nullptr)
|
||||
throw CFException("Invalid casting: compared object does not implement IDirectorEntry interface");
|
||||
|
||||
if (this->getNameLength() > otherDir->getNameLength())
|
||||
{
|
||||
return THIS_IS_GREATER;
|
||||
}
|
||||
else if (this->getNameLength() < otherDir->getNameLength())
|
||||
{
|
||||
return OTHER_IS_GREATER;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wstring thisName = GetEntryName();
|
||||
std::wstring otherName = otherDir->GetEntryName();
|
||||
|
||||
for (int z = 0; z < (int)thisName.size(); z++)
|
||||
{
|
||||
char thisChar = toupper(thisName[z]);
|
||||
char otherChar = toupper(otherName[z]);
|
||||
|
||||
if (thisChar > otherChar)
|
||||
return THIS_IS_GREATER;
|
||||
else if (thisChar < otherChar)
|
||||
return OTHER_IS_GREATER;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ULONG64 DirectoryEntry::fnv_hash(const char *buffer, int lenght)
|
||||
{
|
||||
ULONG64 h = 2166136261;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < lenght; i++)
|
||||
h = (h * 16777619) ^ buffer[i];
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> DirectoryEntry::New(std::wstring name, StgType stgType, SVector<IDirectoryEntry>& dirRepository)
|
||||
{
|
||||
std::shared_ptr<IDirectoryEntry> de;
|
||||
if (/*dirRepository != null*/true)
|
||||
{
|
||||
de.reset(new DirectoryEntry(name, stgType, dirRepository));
|
||||
// No invalid directory entry found
|
||||
dirRepository.push_back(de);
|
||||
de->setSid(dirRepository.size() - 1);
|
||||
}
|
||||
else
|
||||
throw std::invalid_argument("dirRepository Directory repository cannot be null in New() method");
|
||||
|
||||
return de;
|
||||
}
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> DirectoryEntry::TryNew(std::wstring name, StgType stgType, SVector<IDirectoryEntry>& dirRepository)
|
||||
{
|
||||
std::shared_ptr<DirectoryEntry> de(new DirectoryEntry(name, stgType, dirRepository));
|
||||
|
||||
// If we are not adding an invalid dirEntry as
|
||||
// in a normal loading from file (invalid dirs MAY pad a sector)
|
||||
if (de != nullptr)
|
||||
{
|
||||
// Find first available invalid slot (if any) to reuse it
|
||||
for (int i = 0; i < (int)dirRepository.size(); i++)
|
||||
{
|
||||
if (dirRepository[i]->getStgType() == StgType::StgInvalid)
|
||||
{
|
||||
dirRepository[i] = de;
|
||||
de->sid = i;
|
||||
return de;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No invalid directory entry found
|
||||
dirRepository.push_back(de);
|
||||
de->sid = dirRepository.size() - 1;
|
||||
|
||||
return de;
|
||||
}
|
||||
|
||||
std::shared_ptr<IDirectoryEntry> DirectoryEntry::Mock(std::wstring name, StgType stgType)
|
||||
{
|
||||
auto de = std::shared_ptr<IDirectoryEntry>(new DirectoryEntry(name, stgType, {}));
|
||||
|
||||
return de;
|
||||
}
|
||||
|
||||
106
Common/cfcpp/directoryentry.h
Normal file
106
Common/cfcpp/directoryentry.h
Normal file
@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include "svector.h"
|
||||
#include "idirectoryentry.h"
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
class DirectoryEntry : public IDirectoryEntry, protected std::enable_shared_from_this<DirectoryEntry>
|
||||
{
|
||||
public:
|
||||
|
||||
static const int THIS_IS_GREATER = 1;
|
||||
static const int OTHER_IS_GREATER = -1;
|
||||
|
||||
static const int NOSTREAM = 0xFFFFFFFF;
|
||||
static const int ZERO = 0;
|
||||
|
||||
DirectoryEntry(std::wstring name, StgType stgType, SVector<IDirectoryEntry> dirRepository);
|
||||
|
||||
RedBlackTree::PIRBNode getLeft() const override;
|
||||
RedBlackTree::PIRBNode getRight() const override;
|
||||
void setLeft(RedBlackTree::PIRBNode pNode) override;
|
||||
void setRight(RedBlackTree::PIRBNode pNode) override;
|
||||
std::streamsize getSize() const override {return size;}
|
||||
void setSize(std::streamsize value) override {size = value;}
|
||||
int getStateBits() const override {return stateBits;}
|
||||
void setStateBits(int value) override {stateBits = value;}
|
||||
|
||||
inline void setColor(RedBlackTree::Color clr) override {stgColor = (StgColor)clr;}
|
||||
inline RedBlackTree::Color getColor()const override {return (RedBlackTree::Color)stgColor;}
|
||||
|
||||
void setParent(RedBlackTree::PIRBNode pParent) override {parent = pParent;}
|
||||
inline RedBlackTree::PIRBNode getParent() const override {return parent.lock();}
|
||||
inline RedBlackTree::PIRBNode Grandparent() const override
|
||||
{return (parent.use_count() ? parent.lock()->getParent() : RedBlackTree::PIRBNode());}
|
||||
RedBlackTree::PIRBNode Sibling() const override; // check parent before using
|
||||
RedBlackTree::PIRBNode Uncle() const override;
|
||||
void AssignValueTo(RedBlackTree::PIRBNode other) override;
|
||||
|
||||
int CompareTo(const RedBlackTree::PIRBNode& other) const override;
|
||||
std::wstring ToString() const override;
|
||||
|
||||
inline int getChild() const override {return child;}
|
||||
inline void setChild(int value) override {child = value;}
|
||||
|
||||
inline int getLeftSibling() const override {return leftSibling;}
|
||||
inline void setLeftSibling(int value) override {leftSibling = value;}
|
||||
inline int getRightSibling() const override {return rightSibling;}
|
||||
inline void setRightSibling(int value) override {rightSibling = value;}
|
||||
|
||||
inline UINT64 getCreationDate() const override {return creationDate;}
|
||||
inline void setCreationDate(const UINT64& value) override {creationDate = value;}
|
||||
inline UINT64 getModifyDate() const override {return modifyDate;}
|
||||
inline void setModifyDate(const UINT64& value) override {modifyDate = value;}
|
||||
|
||||
int getSid() const override;
|
||||
void setSid(int newSid) override;
|
||||
|
||||
std::wstring GetEntryName() const override;
|
||||
void SetEntryName(const std::wstring &entryName) override;
|
||||
inline ushort getNameLength() const override {return nameLength;}
|
||||
|
||||
void setStartSetc(int value) override {startSetc = value;};
|
||||
int getStartSetc() const override {return startSetc;};
|
||||
|
||||
void Read(Stream stream, CFSVersion ver = CFSVersion::Ver_3) override;
|
||||
void Write(Stream stream) const override;
|
||||
inline StgColor getStgColor() const override {return stgColor;}
|
||||
inline void setStgColor(StgColor value) override {stgColor = value;}
|
||||
inline StgType getStgType() const override {return stgType;}
|
||||
inline void setStgType(StgType value) override {stgType = value;}
|
||||
inline GUID getStorageCLSID() const override {return storageCLSID;}
|
||||
inline void setStorageCLSID(GUID value) override {storageCLSID = value;}
|
||||
int GetHashCode() const override;
|
||||
|
||||
inline std::wstring Name() const {return GetEntryName();}
|
||||
|
||||
public:
|
||||
UINT64 creationDate = 0;
|
||||
UINT64 modifyDate = 0;
|
||||
int startSetc = 0xFFFFFFFE;
|
||||
LONG64 size;
|
||||
int leftSibling = NOSTREAM;
|
||||
int rightSibling = NOSTREAM;
|
||||
int child = NOSTREAM;
|
||||
int stateBits;
|
||||
static std::shared_ptr<IDirectoryEntry> New(std::wstring name, StgType stgType, SVector<IDirectoryEntry>& dirRepository);
|
||||
static std::shared_ptr<IDirectoryEntry> TryNew(std::wstring name, StgType stgType, SVector<IDirectoryEntry> &dirRepository);
|
||||
static std::shared_ptr<IDirectoryEntry> Mock(std::wstring name, StgType stgType);
|
||||
|
||||
private:
|
||||
static ULONG64 fnv_hash(const char *buffer, int lenght);
|
||||
|
||||
private:
|
||||
int sid = -1;
|
||||
char entryName[64];
|
||||
ushort nameLength;
|
||||
StgType stgType = StgType::StgInvalid;
|
||||
StgColor stgColor = StgColor::Red;
|
||||
SVector<IDirectoryEntry> dirRepository;
|
||||
std::weak_ptr<RedBlackTree::IRBNode> parent;
|
||||
GUID storageCLSID;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
31
Common/cfcpp/event.h
Normal file
31
Common/cfcpp/event.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
template <class T>
|
||||
class Event
|
||||
{
|
||||
public:
|
||||
Event& operator+=(T& act)
|
||||
{
|
||||
events.push_back(act);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const Event& operator()()const
|
||||
{
|
||||
for (auto& event : events)
|
||||
event();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
events.clear();
|
||||
}
|
||||
|
||||
inline size_t size() const {return events.size();}
|
||||
|
||||
protected:
|
||||
std::vector<T> events;
|
||||
};
|
||||
40
Common/cfcpp/guid.h
Normal file
40
Common/cfcpp/guid.h
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
struct GUID
|
||||
{
|
||||
unsigned int Data1 = 0;
|
||||
unsigned short Data2 = 0;
|
||||
unsigned short Data3 = 0;
|
||||
unsigned long long Data4 = 0;
|
||||
|
||||
unsigned char* getData4()
|
||||
{
|
||||
return reinterpret_cast<unsigned char*>(&Data4);
|
||||
}
|
||||
|
||||
GUID (const GUID& o) : Data1(o.Data1), Data2(o.Data2), Data3(o.Data3)
|
||||
{
|
||||
}
|
||||
|
||||
GUID& operator=(const GUID& o)
|
||||
{
|
||||
Data1 = o.Data1;
|
||||
Data2 = o.Data2;
|
||||
Data3 = o.Data3;
|
||||
Data4 = o.Data4;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator!=(const GUID& oth)const
|
||||
{
|
||||
return Data1 != oth.Data1 || Data2 != oth.Data2 || Data3 != oth.Data3 || Data4 != oth.Data4;
|
||||
}
|
||||
|
||||
bool operator==(const GUID& oth)const
|
||||
{
|
||||
return !operator!=(oth);
|
||||
}
|
||||
|
||||
GUID (){}
|
||||
};
|
||||
116
Common/cfcpp/header.cpp
Normal file
116
Common/cfcpp/header.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
#include "header.h"
|
||||
#include "cfexception.h"
|
||||
#include "streamrw.h"
|
||||
|
||||
using namespace CFCPP;
|
||||
|
||||
Header::Header() :
|
||||
Header(3)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Header::Header(ushort version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case 3:
|
||||
majorVersion = 3;
|
||||
sectorShift = 0x0009;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
majorVersion = 4;
|
||||
sectorShift = 0x000C;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw CFException("Invalid Compound File Format version");
|
||||
|
||||
|
||||
}
|
||||
|
||||
for (int i = 0; i < 109; i++)
|
||||
{
|
||||
difat[i] = Sector::FREESECT;
|
||||
}
|
||||
}
|
||||
|
||||
void Header::Write(CFCPP::Stream &stream)
|
||||
{
|
||||
StreamRW rw(stream);
|
||||
rw.WriteArray(headerSignature, sizeof(headerSignature));
|
||||
rw.WriteArray(clsid, sizeof(clsid));
|
||||
rw.Write(minorVersion);
|
||||
rw.Write(majorVersion);
|
||||
rw.Write(byteOrder);
|
||||
rw.Write(sectorShift);
|
||||
rw.Write(miniSectorShift);
|
||||
rw.WriteArray(unUsed, sizeof(unUsed));
|
||||
rw.Write(directorySectorsNumber);
|
||||
rw.Write(fatSectorsNumber);
|
||||
rw.Write(firstDirectorySectorID);
|
||||
rw.Write(unUsed2);
|
||||
rw.Write(minSizeStandardStream);
|
||||
rw.Write(firstMiniFATSectorID);
|
||||
rw.Write(miniFATSectorsNumber);
|
||||
rw.Write(firstDIFATSectorID);
|
||||
rw.Write(difatSectorsNumber);
|
||||
|
||||
for (int i : difat)
|
||||
{
|
||||
rw.Write(i);
|
||||
}
|
||||
|
||||
if (majorVersion == 4)
|
||||
{
|
||||
std::vector<BYTE> zeroHead(3584,0);
|
||||
rw.WriteArray(zeroHead.data(), zeroHead.size());
|
||||
}
|
||||
}
|
||||
|
||||
void Header::Read(CFCPP::Stream &stream)
|
||||
{
|
||||
StreamRW rw(stream);
|
||||
|
||||
rw.ReadArray(headerSignature, sizeof(headerSignature));
|
||||
CheckSignature();
|
||||
rw.ReadArray(clsid, sizeof(clsid));
|
||||
minorVersion = rw.Read<decltype(minorVersion)>();
|
||||
majorVersion = rw.Read<decltype(majorVersion)>();
|
||||
CheckVersion();
|
||||
byteOrder = rw.Read<decltype(majorVersion)>();
|
||||
sectorShift = rw.Read<decltype(majorVersion)>();
|
||||
miniSectorShift = rw.Read<decltype(majorVersion)>();
|
||||
rw.ReadArray(unUsed, sizeof (unUsed));
|
||||
directorySectorsNumber = rw.Read<decltype(directorySectorsNumber)>();
|
||||
fatSectorsNumber = rw.Read<decltype(fatSectorsNumber)>();
|
||||
firstDirectorySectorID = rw.Read<decltype(firstDirectorySectorID)>();
|
||||
unUsed2 = rw.Read<decltype(unUsed2)>();
|
||||
minSizeStandardStream = rw.Read<decltype(minSizeStandardStream)>();
|
||||
firstMiniFATSectorID = rw.Read<decltype(firstMiniFATSectorID)>();
|
||||
miniFATSectorsNumber = rw.Read<decltype(miniFATSectorsNumber)>();
|
||||
firstDIFATSectorID = rw.Read<decltype(firstDIFATSectorID)>();
|
||||
difatSectorsNumber = rw.Read<decltype(difatSectorsNumber)>();
|
||||
|
||||
for (int i = 0; i < 109; i++)
|
||||
{
|
||||
difat[i] = rw.Read<int>();
|
||||
}
|
||||
}
|
||||
|
||||
void Header::CheckVersion() const
|
||||
{
|
||||
if (majorVersion != 3 && majorVersion != 4)
|
||||
throw CFFileFormatException("Unsupported Binary File Format version: OpenMcdf only supports Compound Files with major version equal to 3 or 4 ");
|
||||
}
|
||||
|
||||
void Header::CheckSignature() const
|
||||
{
|
||||
std::array<BYTE,8> OLE_CFS_SIGNATURE{ 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
|
||||
for (size_t i = 0; i < sizeof(headerSignature); i++)
|
||||
{
|
||||
if (headerSignature[i] != OLE_CFS_SIGNATURE[i])
|
||||
throw CFFileFormatException("Invalid OLE structured storage file");
|
||||
}
|
||||
}
|
||||
41
Common/cfcpp/header.h
Normal file
41
Common/cfcpp/header.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "sector.h"
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
class Header
|
||||
{
|
||||
public:
|
||||
Header();
|
||||
Header(ushort version);
|
||||
void Write(Stream& stream);
|
||||
void Read(Stream& stream);
|
||||
|
||||
private:
|
||||
void CheckVersion()const;
|
||||
void CheckSignature()const;
|
||||
|
||||
public:
|
||||
BYTE headerSignature[8] = {0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1};
|
||||
BYTE clsid[16];
|
||||
USHORT minorVersion = 0x003E;
|
||||
USHORT majorVersion = 0x0003;
|
||||
USHORT byteOrder = 0xFFFE;
|
||||
USHORT sectorShift = 9;
|
||||
USHORT miniSectorShift = 6;
|
||||
BYTE unUsed[6];
|
||||
INT directorySectorsNumber;
|
||||
INT fatSectorsNumber;
|
||||
INT firstDirectorySectorID = Sector::ENDOFCHAIN;
|
||||
uint unUsed2;
|
||||
uint minSizeStandardStream = 4096;
|
||||
INT firstMiniFATSectorID = 0xFFFFFFFE;
|
||||
uint miniFATSectorsNumber;
|
||||
INT firstDIFATSectorID = Sector::ENDOFCHAIN;
|
||||
uint difatSectorsNumber;
|
||||
INT difat[109];
|
||||
};
|
||||
|
||||
}
|
||||
75
Common/cfcpp/idirectoryentry.h
Normal file
75
Common/cfcpp/idirectoryentry.h
Normal file
@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include "stream.h"
|
||||
#include "RBTree/irbnode.h"
|
||||
#include "guid.h"
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
enum StgType : int
|
||||
{
|
||||
StgInvalid = 0,
|
||||
StgStorage = 1,
|
||||
StgStream = 2,
|
||||
StgLockbytes = 3,
|
||||
StgProperty = 4,
|
||||
StgRoot = 5
|
||||
};
|
||||
|
||||
enum StgColor : int
|
||||
{
|
||||
Red = 0,
|
||||
Black = 1
|
||||
};
|
||||
|
||||
enum CFSVersion : int
|
||||
{
|
||||
/// Compound file version 3 - The default and most common version available. Sector size 512 bytes, 2GB max file size.
|
||||
Ver_3 = 3,
|
||||
/// Compound file version 4 - Sector size is 4096 bytes. Using this version could bring some compatibility problem with existing applications.
|
||||
Ver_4 = 4
|
||||
};
|
||||
|
||||
class IDirectoryEntry : public RedBlackTree::IRBNode
|
||||
{
|
||||
public:
|
||||
|
||||
virtual int getChild() const = 0;
|
||||
virtual void setChild(int value) = 0;
|
||||
virtual int getLeftSibling() const = 0;
|
||||
virtual void setLeftSibling(int value) = 0;
|
||||
virtual int getRightSibling() const = 0;
|
||||
virtual void setRightSibling(int value) = 0;
|
||||
virtual std::streamsize getSize() const = 0;
|
||||
virtual void setSize(std::streamsize value) = 0;
|
||||
virtual int getStateBits() const = 0;
|
||||
virtual void setStateBits(int value) = 0;
|
||||
|
||||
|
||||
virtual UINT64 getCreationDate() const = 0;
|
||||
virtual void setCreationDate(const UINT64& value) = 0;
|
||||
virtual UINT64 getModifyDate() const = 0;
|
||||
virtual void setModifyDate(const UINT64& value) = 0;
|
||||
|
||||
virtual int getSid() const = 0;
|
||||
virtual void setSid(int newSid) = 0;
|
||||
|
||||
virtual std::wstring GetEntryName() const = 0;
|
||||
virtual void SetEntryName(const std::wstring &entryName) = 0;
|
||||
virtual ushort getNameLength() const = 0;
|
||||
|
||||
virtual void setStartSetc(int value) = 0;
|
||||
virtual int getStartSetc() const = 0;
|
||||
|
||||
virtual void Read(Stream stream, CFSVersion ver = CFSVersion::Ver_3) = 0;
|
||||
virtual void Write(Stream stream) const = 0;
|
||||
virtual StgColor getStgColor() const = 0;
|
||||
virtual void setStgColor(StgColor value) = 0;
|
||||
virtual StgType getStgType() const = 0;
|
||||
virtual void setStgType(StgType value) = 0;
|
||||
virtual GUID getStorageCLSID() const = 0;
|
||||
virtual void setStorageCLSID(GUID value) = 0;
|
||||
virtual int GetHashCode() const = 0;
|
||||
};
|
||||
}
|
||||
90
Common/cfcpp/sector.cpp
Normal file
90
Common/cfcpp/sector.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
#include "sector.h"
|
||||
|
||||
using namespace CFCPP;
|
||||
|
||||
int Sector::MINISECTOR_SIZE = 64;
|
||||
|
||||
Sector::Sector(int size, const Stream stream) :
|
||||
size(size), stream(stream)
|
||||
{}
|
||||
|
||||
Sector::Sector(int size, const std::vector<BYTE>& data) :
|
||||
size(size), data(data)
|
||||
{}
|
||||
|
||||
Sector::Sector(int size) :
|
||||
size(size)
|
||||
{}
|
||||
|
||||
|
||||
bool Sector::IsStreamed()
|
||||
{
|
||||
if (stream == nullptr || size == MINISECTOR_SIZE)
|
||||
return false;
|
||||
|
||||
auto fileSize = Length(stream);
|
||||
return (this->id * size) + size < fileSize;
|
||||
}
|
||||
|
||||
void Sector::ZeroData()
|
||||
{
|
||||
std::fill(data.begin(), data.end(), 0);
|
||||
dirtyFlag = true;
|
||||
}
|
||||
|
||||
void Sector::InitFATData()
|
||||
{
|
||||
std::fill(data.begin(), data.end(), 0xff);
|
||||
dirtyFlag = true;
|
||||
}
|
||||
|
||||
void Sector::ReleaseData()
|
||||
{
|
||||
data.clear();
|
||||
}
|
||||
|
||||
void Sector::Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockObject);
|
||||
if (disposing)
|
||||
{
|
||||
// Call from user code...
|
||||
|
||||
|
||||
}
|
||||
|
||||
data.clear();
|
||||
dirtyFlag = false;
|
||||
id = ENDOFCHAIN;
|
||||
size = 0;
|
||||
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
std::vector<BYTE> &Sector::GetData()
|
||||
{
|
||||
if (data.empty())
|
||||
{
|
||||
data = std::vector<BYTE>(size, 0);
|
||||
if (IsStreamed())
|
||||
{
|
||||
stream->seek(size + id * size, std::ios_base::beg);
|
||||
stream->read(reinterpret_cast<char*>(data.data()), size);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
int Sector::getSize() const
|
||||
{
|
||||
return size;
|
||||
}
|
||||
58
Common/cfcpp/sector.h
Normal file
58
Common/cfcpp/sector.h
Normal file
@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include "stream.h"
|
||||
#include "../../DesktopEditor/common/Types.h"
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
|
||||
enum SectorType
|
||||
{
|
||||
Normal,
|
||||
Mini,
|
||||
FAT,
|
||||
DIFAT,
|
||||
RangeLockSector,
|
||||
Directory
|
||||
};
|
||||
|
||||
class Sector
|
||||
{
|
||||
public:
|
||||
Sector(int size, const Stream stream);
|
||||
Sector(int size, const std::vector<BYTE> &data);
|
||||
Sector(int size);
|
||||
|
||||
bool IsStreamed();
|
||||
void ZeroData();
|
||||
void InitFATData();
|
||||
void ReleaseData();
|
||||
|
||||
virtual void Dispose(bool disposing=false);
|
||||
std::vector<BYTE> &GetData();
|
||||
|
||||
public:
|
||||
static int MINISECTOR_SIZE;
|
||||
const static int FREESECT = 0xFFFFFFFF;
|
||||
const static int ENDOFCHAIN = 0xFFFFFFFE;
|
||||
const static int FATSECT = 0xFFFFFFFD;
|
||||
const static int DIFSECT = 0xFFFFFFFC;
|
||||
|
||||
int getSize() const;
|
||||
|
||||
SectorType type;
|
||||
bool dirtyFlag = false;
|
||||
int id = -1;
|
||||
|
||||
private:
|
||||
int size = 0;
|
||||
Stream stream;
|
||||
std::vector<BYTE> data;
|
||||
std::mutex lockObject;
|
||||
bool _disposed;//false
|
||||
};
|
||||
|
||||
}
|
||||
65
Common/cfcpp/sectorcollection.cpp
Normal file
65
Common/cfcpp/sectorcollection.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include "sectorcollection.h"
|
||||
|
||||
using namespace CFCPP;
|
||||
|
||||
SectorCollection::SectorCollection()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SectorCollection::Add(std::shared_ptr<Sector> item)
|
||||
{
|
||||
OnVer3SizeLimitReached();
|
||||
|
||||
add(item);
|
||||
}
|
||||
|
||||
void SectorCollection::Clear()
|
||||
{
|
||||
largeArraySlices.clear();
|
||||
count = 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> SectorCollection::operator[](size_t index)
|
||||
{
|
||||
size_t globalPos = 0;
|
||||
for (size_t i = 0; i < largeArraySlices.size(); i ++)
|
||||
{
|
||||
size_t sliceSize = largeArraySlices[i].size();
|
||||
globalPos += sliceSize;
|
||||
if (globalPos < index)
|
||||
return largeArraySlices[i][i % sliceSize];
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void SectorCollection::DoCheckSizeLimitReached()
|
||||
{
|
||||
if (OnVer3SizeLimitReached.size() && !sizeLimitReached && (count - 1 > MAX_SECTOR_V4_COUNT_LOCK_RANGE))
|
||||
{
|
||||
sizeLimitReached = true;
|
||||
OnVer3SizeLimitReached();
|
||||
}
|
||||
}
|
||||
|
||||
int SectorCollection::add(std::shared_ptr<Sector> item)
|
||||
{
|
||||
unsigned itemIndex = count / SLICE_SIZE;
|
||||
|
||||
if (itemIndex < largeArraySlices.size())
|
||||
{
|
||||
largeArraySlices[itemIndex].push_back(item);
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// std::unique_ptr<std::vector<Sector>> ar(new std::vector<Sector>(SLICE_SIZE));
|
||||
SVector<Sector> ar;
|
||||
ar.push_back(item);
|
||||
largeArraySlices.push_back(ar);
|
||||
count++;
|
||||
}
|
||||
|
||||
return count - 1;
|
||||
}
|
||||
34
Common/cfcpp/sectorcollection.h
Normal file
34
Common/cfcpp/sectorcollection.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "sector.h"
|
||||
#include <list>
|
||||
#include "svector.h"
|
||||
#include "event.h"
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
using Ver3SizeLimitReached = std::function<void()>;
|
||||
|
||||
class SectorCollection
|
||||
{
|
||||
public:
|
||||
std::vector<SVector<Sector>> largeArraySlices;
|
||||
SectorCollection();
|
||||
void Add(std::shared_ptr<Sector> item);
|
||||
void Clear();
|
||||
inline int Count()const {return count;}
|
||||
std::shared_ptr<Sector> operator[](size_t index);
|
||||
Event<Ver3SizeLimitReached> OnVer3SizeLimitReached;
|
||||
|
||||
private:
|
||||
void DoCheckSizeLimitReached();
|
||||
int add(std::shared_ptr<Sector> item);
|
||||
private:
|
||||
const int MAX_SECTOR_V4_COUNT_LOCK_RANGE = 524287; //0x7FFFFF00 for Version 4
|
||||
const int SLICE_SIZE = 4096;
|
||||
bool sizeLimitReached = false;
|
||||
|
||||
int count = 0;
|
||||
|
||||
};
|
||||
}
|
||||
35
Common/cfcpp/slist.h
Normal file
35
Common/cfcpp/slist.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <list>
|
||||
|
||||
template <class T>
|
||||
class SList : public std::list<std::shared_ptr<T>>
|
||||
{
|
||||
public:
|
||||
template<class P>
|
||||
SList<P> cast() const
|
||||
{
|
||||
SList<P> res;
|
||||
for (const auto& spEl : *this)
|
||||
res.push_back(std::dynamic_pointer_cast<P>(spEl));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<T> dequeue()
|
||||
{
|
||||
if (this->empty())
|
||||
return {};
|
||||
|
||||
auto spEl = this->front();
|
||||
this->pop_front();
|
||||
|
||||
return spEl;
|
||||
}
|
||||
|
||||
inline void enqueue(std::shared_ptr<T> el)
|
||||
{
|
||||
this->push_back(el);
|
||||
}
|
||||
};
|
||||
107
Common/cfcpp/stream.cpp
Normal file
107
Common/cfcpp/stream.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#include "stream.h"
|
||||
#include "../../DesktopEditor/common/File.h"
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
std::streamsize CFCPP::Length(const CFCPP::Stream& st)
|
||||
{
|
||||
if (st.get() == nullptr)
|
||||
return 0;
|
||||
|
||||
auto curPos = st->tell();
|
||||
st->seek(0, std::ios_base::end);
|
||||
auto ssize = st->tell();
|
||||
st->seek(curPos);
|
||||
|
||||
return ssize;
|
||||
}
|
||||
|
||||
CFCPP::Stream CFCPP::OpenFileStream(std::wstring filename, bool bRewrite, bool trunc)
|
||||
{
|
||||
BYTE* pUtf8 = nullptr;
|
||||
std::streamsize lLen = 0;
|
||||
NSFile::CUtf8Converter::GetUtf8StringFromUnicode(filename.c_str(), filename.length(), pUtf8, lLen, false);
|
||||
std::string utf8filename(pUtf8, pUtf8 + lLen);
|
||||
delete [] pUtf8;
|
||||
|
||||
return OpenFileStream(utf8filename, bRewrite, trunc);
|
||||
}
|
||||
|
||||
CFCPP::Stream CFCPP::OpenFileStream(std::string filename, bool bRewrite, bool trunc)
|
||||
{
|
||||
CFCPP::Stream st;
|
||||
|
||||
// it's not good, but otherwise file doesn't create or if use ios::app, then the seek for writing will be blocked
|
||||
if (bRewrite)
|
||||
std::fstream create(filename, std::ios::app | std::ios::out);
|
||||
|
||||
if (trunc && bRewrite)
|
||||
st.reset(new FStreamWrapper(filename, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc));
|
||||
else if (bRewrite)
|
||||
st.reset(new FStreamWrapper(filename, std::ios::binary | std::ios::in | std::ios::out));
|
||||
else
|
||||
st.reset(new FStreamWrapper(filename, std::ios::binary | std::ios::in));
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
bool CFCPP::IsOpen(const Stream &st)
|
||||
{
|
||||
if (std::dynamic_pointer_cast<FStreamWrapper>(st))
|
||||
return std::static_pointer_cast<FStreamWrapper>(st)->is_open();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string CFCPP::CorrectUnixPath(const std::string original)
|
||||
{
|
||||
#if !defined(_WIN32) && !defined (_WIN64)
|
||||
return original;
|
||||
#else
|
||||
auto str = original;
|
||||
std::replace(str.begin(), str.end(), '/', '\\');
|
||||
return str;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
int CFCPP::FileLenght(std::wstring filename)
|
||||
{
|
||||
auto stream = OpenFileStream(filename);
|
||||
auto lenght = Length(stream);
|
||||
stream->close();
|
||||
|
||||
return lenght;
|
||||
}
|
||||
|
||||
ULONG64 CFCPP::FileSimpleHash(std::wstring filename, int len, int offset)
|
||||
{
|
||||
|
||||
auto stream = OpenFileStream(filename);
|
||||
if (!IsOpen(stream))
|
||||
return 0;
|
||||
|
||||
if (len < 0)
|
||||
len = Length(stream);
|
||||
|
||||
stream->seek(offset);
|
||||
|
||||
ULONG64 h = 2166136261;
|
||||
constexpr int bufLen = 0x2000;
|
||||
char buffer[bufLen];
|
||||
while (len > 0)
|
||||
{
|
||||
memset(buffer, 0, bufLen);
|
||||
int readLen = std::min(bufLen, len);
|
||||
stream->read(buffer, readLen);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < readLen; i++)
|
||||
h = (h * 16777619) ^ buffer[i];
|
||||
|
||||
len -= readLen;
|
||||
}
|
||||
|
||||
|
||||
return h;
|
||||
}
|
||||
51
Common/cfcpp/stream.h
Normal file
51
Common/cfcpp/stream.h
Normal file
@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include "../../DesktopEditor/common/Types.h"
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
|
||||
class IStream
|
||||
{
|
||||
public:
|
||||
virtual std::streamsize tell() = 0;
|
||||
virtual std::streamsize seek(std::streamsize offset, std::ios_base::seekdir mode = std::ios::beg) = 0;
|
||||
virtual std::streamsize read(char* buffer, std::streamsize len) = 0;
|
||||
virtual void write (const char* buffer, std::streamsize len) = 0;
|
||||
virtual void flush() = 0;
|
||||
virtual void close() = 0;
|
||||
};
|
||||
|
||||
using Stream = std::shared_ptr<IStream>;
|
||||
|
||||
class FStreamWrapper : public IStream, public std::fstream
|
||||
{
|
||||
public:
|
||||
FStreamWrapper(std::string filename, std::ios_base::openmode openmode) :
|
||||
std::fstream(filename, openmode) {}
|
||||
|
||||
inline std::streamsize tell() override { return std::fstream::tellg(); }
|
||||
inline std::streamsize seek(std::streamsize offset, std::ios_base::seekdir mode = std::ios::beg) override
|
||||
{ std::fstream::seekp(offset, mode); std::fstream::seekg(offset, mode); return tell();}
|
||||
inline std::streamsize read(char* buffer, std::streamsize len) override { std::fstream::read(buffer, len); return tell(); }
|
||||
inline void write (const char* buffer, std::streamsize len) override { std::fstream::write(buffer, len); }
|
||||
inline void flush() override { std::fstream::flush(); }
|
||||
inline void close() override { std::fstream::close(); }
|
||||
};
|
||||
|
||||
std::string CorrectUnixPath(const std::string original);
|
||||
|
||||
Stream OpenFileStream(std::wstring filename, bool bRewrite = false, bool trunc = false);
|
||||
Stream OpenFileStream(std::string filename, bool bRewrite = false, bool trunc = false);
|
||||
|
||||
bool IsOpen(const Stream& st);
|
||||
std::streamsize Length(const Stream& st);
|
||||
int FileLenght(std::wstring filename);
|
||||
|
||||
ULONG64 FileSimpleHash(std::wstring filename, int len = -1, int offset = 0);
|
||||
|
||||
}
|
||||
40
Common/cfcpp/streamrw.cpp
Normal file
40
Common/cfcpp/streamrw.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#include "streamrw.h"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace CFCPP;
|
||||
|
||||
StreamRW::StreamRW(CFCPP::Stream stream)
|
||||
: stream(stream)
|
||||
{
|
||||
}
|
||||
|
||||
T_LONG64 StreamRW::Seek(T_LONG64 offset)
|
||||
{
|
||||
stream->seek(offset, std::ios::beg);
|
||||
return stream->tell();
|
||||
}
|
||||
|
||||
T_LONG64 CFCPP::StreamRW::Tell()
|
||||
{
|
||||
return stream->tell();
|
||||
}
|
||||
|
||||
void StreamRW::ReadArray(char *data, int lenght)
|
||||
{
|
||||
stream->read(data, lenght);
|
||||
}
|
||||
|
||||
void StreamRW::ReadArray(BYTE* data, int lenght)
|
||||
{
|
||||
stream->read(reinterpret_cast<char*>(data), lenght);
|
||||
}
|
||||
|
||||
void StreamRW::WriteArray(const BYTE *arr, int lenght)
|
||||
{
|
||||
stream->write(reinterpret_cast<const char*>(arr), lenght);
|
||||
}
|
||||
|
||||
void StreamRW::WriteArray(const char *arr, int lenght)
|
||||
{
|
||||
stream->write(arr, lenght);
|
||||
}
|
||||
45
Common/cfcpp/streamrw.h
Normal file
45
Common/cfcpp/streamrw.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "stream.h"
|
||||
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
class StreamRW
|
||||
{
|
||||
public:
|
||||
StreamRW(Stream stream);
|
||||
T_LONG64 Seek(T_LONG64 offset);
|
||||
T_LONG64 Tell();
|
||||
|
||||
template <class T>
|
||||
T Read()
|
||||
{
|
||||
T value;
|
||||
char* asByteArr = reinterpret_cast<char*>(&value);
|
||||
stream->read(asByteArr, sizeof (T));
|
||||
return value;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void Write(T value)
|
||||
{
|
||||
char* asByteArr = reinterpret_cast<char*>(&value);
|
||||
stream->write(asByteArr, sizeof (T));
|
||||
}
|
||||
|
||||
void ReadArray(char* data, int lenght);
|
||||
void ReadArray(BYTE* data, int lenght);
|
||||
void WriteArray(const BYTE *arr, int lenght);
|
||||
void WriteArray(const char *arr, int lenght);
|
||||
|
||||
inline void Close(){return;}
|
||||
|
||||
private:
|
||||
Stream stream;
|
||||
};
|
||||
|
||||
}
|
||||
245
Common/cfcpp/streamview.cpp
Normal file
245
Common/cfcpp/streamview.cpp
Normal file
@ -0,0 +1,245 @@
|
||||
#include "streamview.h"
|
||||
#include "cfexception.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace CFCPP;
|
||||
|
||||
StreamView::StreamView(const SVector<Sector> §orChain, int sectorSize, Stream stream)
|
||||
: sectorSize(sectorSize), sectorChain(sectorChain), stream(stream)
|
||||
{
|
||||
// if (sectorChain == null)
|
||||
// throw CFException("Sector Chain cannot be null");
|
||||
|
||||
auto pos = stream->tell();
|
||||
if (sectorSize <= 0)
|
||||
throw CFException("Sector size must be greater than zero");
|
||||
}
|
||||
|
||||
StreamView::StreamView(const SVector<Sector> §orChain, int sectorSize, std::streamsize length,
|
||||
SList<Sector> &availableSectors, Stream stream, bool isFatStream) :
|
||||
StreamView(sectorChain, sectorSize, stream)
|
||||
{
|
||||
this->isFatStream = isFatStream;
|
||||
adjustLength(length, availableSectors);
|
||||
|
||||
}
|
||||
|
||||
std::streamsize StreamView::tell()
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
void StreamView::write(const char *buffer, std::streamsize count)
|
||||
{
|
||||
int byteWritten = 0;
|
||||
int roundByteWritten = 0;
|
||||
int offset = 0;
|
||||
|
||||
// Assure length
|
||||
if ((position + count) > length)
|
||||
adjustLength((position + count));
|
||||
|
||||
if (sectorChain.empty() == false)
|
||||
{
|
||||
// First sector
|
||||
int secOffset = (int)(position / (std::streamsize)sectorSize);
|
||||
int secShift = (int)(position % sectorSize);
|
||||
|
||||
roundByteWritten = (int)std::min(sectorSize - (position % (std::streamsize)sectorSize), count);
|
||||
|
||||
if (secOffset < (int)sectorChain.size())
|
||||
{
|
||||
char* dst = reinterpret_cast<char*>(sectorChain[secOffset]->GetData().data());
|
||||
std::copy(buffer+offset, buffer+offset+roundByteWritten, dst + secShift);
|
||||
|
||||
sectorChain[secOffset]->dirtyFlag = true;
|
||||
}
|
||||
|
||||
byteWritten += roundByteWritten;
|
||||
offset += roundByteWritten;
|
||||
secOffset++;
|
||||
|
||||
// Central sectors
|
||||
while (byteWritten < (count - sectorSize))
|
||||
{
|
||||
roundByteWritten = sectorSize;
|
||||
char* dst = reinterpret_cast<char*>(sectorChain[secOffset]->GetData().data());
|
||||
std::copy(buffer+offset, buffer+offset+roundByteWritten, dst);
|
||||
|
||||
sectorChain[secOffset]->dirtyFlag = true;
|
||||
|
||||
byteWritten += roundByteWritten;
|
||||
offset += roundByteWritten;
|
||||
secOffset++;
|
||||
}
|
||||
|
||||
// Last sector
|
||||
roundByteWritten = count - byteWritten;
|
||||
|
||||
if (roundByteWritten != 0)
|
||||
{
|
||||
|
||||
char* dst = reinterpret_cast<char*>(sectorChain[secOffset]->GetData().data());
|
||||
std::copy(buffer+offset, buffer+offset+roundByteWritten, dst);
|
||||
|
||||
sectorChain[secOffset]->dirtyFlag = true;
|
||||
}
|
||||
|
||||
position += count;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void StreamView::close()
|
||||
{
|
||||
if (std::dynamic_pointer_cast<std::iostream>(stream) != nullptr)
|
||||
stream->close();
|
||||
}
|
||||
|
||||
std::streamsize StreamView::read(char *buffer, std::streamsize len)
|
||||
{
|
||||
int nRead = 0;
|
||||
int nToRead = 0;
|
||||
int offset = 0;
|
||||
if (sectorChain.empty() == false && sectorChain.size() > 0)
|
||||
{
|
||||
// First sector
|
||||
int secIndex = (int)(position / (std::streamsize)sectorSize);
|
||||
|
||||
// Bytes to read count is the min between request count
|
||||
// and sector border
|
||||
|
||||
nToRead = std::min((int)sectorChain[0]->GetData().size() - ((int)position % sectorSize), (int)len);
|
||||
|
||||
if (secIndex < (int)sectorChain.size())
|
||||
{
|
||||
char* src = reinterpret_cast<char*>(sectorChain[secIndex]->GetData().data() + (int)(position % sectorSize));
|
||||
char* dst = buffer + offset;
|
||||
std::copy(src, src + nToRead, dst);
|
||||
}
|
||||
|
||||
nRead += nToRead;
|
||||
|
||||
secIndex++;
|
||||
|
||||
// Central sectors
|
||||
while (nRead < (len - sectorSize))
|
||||
{
|
||||
nToRead = sectorSize;
|
||||
char* src = reinterpret_cast<char*>(sectorChain[secIndex]->GetData().data());
|
||||
char* dst = buffer + offset + nToRead;
|
||||
std::copy(src, src + nToRead, dst);
|
||||
|
||||
nRead += nToRead;
|
||||
secIndex++;
|
||||
}
|
||||
|
||||
// Last sector
|
||||
nToRead = len - nRead;
|
||||
|
||||
if (nToRead != 0)
|
||||
{
|
||||
if (secIndex > (int)sectorChain.size()) throw CFCorruptedFileException("The file is probably corrupted.");
|
||||
char* src = reinterpret_cast<char*>(sectorChain[secIndex]->GetData().data());
|
||||
char* dst = buffer + offset + nRead;
|
||||
std::copy(src, src + nToRead, dst);
|
||||
|
||||
nRead += nToRead;
|
||||
}
|
||||
|
||||
position += nRead;
|
||||
|
||||
return nRead;
|
||||
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::streamsize StreamView::seek(std::streamsize offset, std::ios_base::seekdir mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case std::ios_base::beg:
|
||||
position = offset;
|
||||
break;
|
||||
|
||||
case std::ios_base::cur:
|
||||
position += offset;
|
||||
break;
|
||||
|
||||
case std::ios_base::end:
|
||||
default:
|
||||
position = length - offset;
|
||||
}
|
||||
|
||||
adjustLength(position);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
void StreamView::SetLength(std::streamsize value)
|
||||
{
|
||||
adjustLength(value);
|
||||
}
|
||||
|
||||
int StreamView::ReadInt32()
|
||||
{
|
||||
read(reinterpret_cast<char*>(&buf), 4);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void StreamView::WriteInt32(int val)
|
||||
{
|
||||
buf = ((val & 0xFF) << 24) | ((val & 0x00FF) << 16) | ((val & 0x0000FF) << 8) | (val & 0x000000FF);
|
||||
write(reinterpret_cast<char*>(&buf), 4);
|
||||
}
|
||||
|
||||
void StreamView::adjustLength(std::streamsize value)
|
||||
{
|
||||
SList<Sector> q;
|
||||
adjustLength(value, q);
|
||||
}
|
||||
|
||||
void StreamView::adjustLength(std::streamsize value, SList<Sector> &availableSectors)
|
||||
{
|
||||
this->length = value;
|
||||
|
||||
std::streamsize delta = value - ((std::streamsize)this->sectorChain.size() * (std::streamsize)sectorSize);
|
||||
|
||||
if (delta > 0)
|
||||
{
|
||||
// enlargment required
|
||||
|
||||
int nSec = (int)std::ceil(((double)delta / sectorSize));
|
||||
|
||||
while (nSec > 0)
|
||||
{
|
||||
std::shared_ptr<Sector> t;
|
||||
|
||||
if (availableSectors.empty() || availableSectors.size() == 0)
|
||||
{
|
||||
t.reset(new Sector(sectorSize, stream));
|
||||
|
||||
if (sectorSize == Sector::MINISECTOR_SIZE)
|
||||
t->type = SectorType::Mini;
|
||||
}
|
||||
else
|
||||
{
|
||||
t = availableSectors.dequeue();
|
||||
}
|
||||
|
||||
if (isFatStream)
|
||||
{
|
||||
t->InitFATData();
|
||||
}
|
||||
sectorChain.push_back(t);
|
||||
nSec--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::streamsize StreamView::getLength() const
|
||||
{
|
||||
return length;
|
||||
}
|
||||
48
Common/cfcpp/streamview.h
Normal file
48
Common/cfcpp/streamview.h
Normal file
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include "sector.h"
|
||||
#include "slist.h"
|
||||
#include "svector.h"
|
||||
|
||||
namespace CFCPP
|
||||
{
|
||||
class StreamView : public IStream
|
||||
{
|
||||
public:
|
||||
StreamView(const SVector<Sector> §orChain, int sectorSize, Stream stream);
|
||||
StreamView(const SVector<Sector> §orChain, int sectorSize, std::streamsize length,
|
||||
SList<Sector> &availableSectors, Stream stream, bool isFatStream = false);
|
||||
|
||||
std::streamsize tell() override;
|
||||
std::streamsize seek(std::streamsize offset, std::ios_base::seekdir mode = std::ios::beg) override;
|
||||
std::streamsize read(char *buffer, std::streamsize count) override;
|
||||
void write(const char *buffer, std::streamsize count) override;
|
||||
void flush() override {}
|
||||
void close() override;
|
||||
|
||||
void SetLength(std::streamsize value);
|
||||
std::streamsize getLength() const;
|
||||
inline SVector<Sector>& BaseSectorChain() {return sectorChain;}
|
||||
|
||||
int ReadInt32();
|
||||
void WriteInt32(int val);
|
||||
|
||||
private:
|
||||
void adjustLength(std::streamsize value);
|
||||
void adjustLength(std::streamsize value, SList<Sector> &availableSectors);
|
||||
private:
|
||||
int sectorSize;
|
||||
std::streamsize length;
|
||||
|
||||
SVector<Sector> sectorChain;
|
||||
bool isFatStream = false;
|
||||
int buf = 0;
|
||||
|
||||
public:
|
||||
Stream stream;
|
||||
SList<Sector> freeSectors;
|
||||
std::streamsize position = 0;
|
||||
};
|
||||
}
|
||||
24
Common/cfcpp/svector.h
Normal file
24
Common/cfcpp/svector.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
template <class T>
|
||||
class SVector : public std::vector<std::shared_ptr<T>>
|
||||
{
|
||||
public:
|
||||
SVector(size_t res = 0) : std::vector<std::shared_ptr<T>>(res)
|
||||
{
|
||||
|
||||
}
|
||||
template<class P>
|
||||
SVector<P> cast() const
|
||||
{
|
||||
auto sz = this->size();
|
||||
SVector<P> res(sz);
|
||||
for (size_t i = 0; i < sz; i++)
|
||||
res[i] = std::dynamic_pointer_cast<P> ((*this)[i]);
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
73
Common/cfcpp/test/.gitignore
vendored
Normal file
73
Common/cfcpp/test/.gitignore
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
BIN
Common/cfcpp/test/data/ex.ppt
Normal file
BIN
Common/cfcpp/test/data/ex.ppt
Normal file
Binary file not shown.
41
Common/cfcpp/test/gtest_dependency.pri
Normal file
41
Common/cfcpp/test/gtest_dependency.pri
Normal file
@ -0,0 +1,41 @@
|
||||
isEmpty(GOOGLETEST_DIR):GOOGLETEST_DIR=$$(GOOGLETEST_DIR)
|
||||
|
||||
isEmpty(GOOGLETEST_DIR) {
|
||||
GOOGLETEST_DIR = $$PWD/../../3dParty/v8_89/v8/third_party/googletest/src
|
||||
!isEmpty(GOOGLETEST_DIR) {
|
||||
warning("Using googletest src dir specified at Qt Creator wizard")
|
||||
message("set GOOGLETEST_DIR as environment variable or qmake variable to get rid of this message")
|
||||
}
|
||||
}
|
||||
|
||||
!isEmpty(GOOGLETEST_DIR): {
|
||||
GTEST_SRCDIR = $$GOOGLETEST_DIR/googletest
|
||||
GMOCK_SRCDIR = $$GOOGLETEST_DIR/googlemock
|
||||
} else: unix {
|
||||
exists(/usr/src/gtest):GTEST_SRCDIR=/usr/src/gtest
|
||||
exists(/usr/src/gmock):GMOCK_SRCDIR=/usr/src/gmock
|
||||
!isEmpty(GTEST_SRCDIR): message("Using gtest from system")
|
||||
}
|
||||
|
||||
requires(exists($$GTEST_SRCDIR):exists($$GMOCK_SRCDIR))
|
||||
|
||||
DEFINES += \
|
||||
GTEST_LANG_CXX11
|
||||
|
||||
!isEmpty(GTEST_SRCDIR) {
|
||||
INCLUDEPATH *= \
|
||||
$$GTEST_SRCDIR \
|
||||
$$GTEST_SRCDIR/include
|
||||
|
||||
SOURCES += \
|
||||
$$GTEST_SRCDIR/src/gtest-all.cc
|
||||
}
|
||||
|
||||
!isEmpty(GMOCK_SRCDIR) {
|
||||
INCLUDEPATH *= \
|
||||
$$GMOCK_SRCDIR \
|
||||
$$GMOCK_SRCDIR/include
|
||||
|
||||
SOURCES += \
|
||||
$$GMOCK_SRCDIR/src/gmock-all.cc
|
||||
}
|
||||
16
Common/cfcpp/test/main.cpp
Normal file
16
Common/cfcpp/test/main.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "tst_streamrw.h"
|
||||
#include "tst_header.h"
|
||||
#include "tst_directoryentry.h"
|
||||
#include "tst_compondfile.h"
|
||||
|
||||
using namespace CFCPP;
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef _LINUX
|
||||
system("pwd");
|
||||
#endif
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
25
Common/cfcpp/test/test.pro
Normal file
25
Common/cfcpp/test/test.pro
Normal file
@ -0,0 +1,25 @@
|
||||
include(gtest_dependency.pri)
|
||||
|
||||
TARGET = test
|
||||
TEMPLATE = app
|
||||
CONFIG += console c++11
|
||||
CONFIG -= app_bundle
|
||||
CONFIG += thread
|
||||
CONFIG -= qt
|
||||
|
||||
CORE_ROOT_DIR = $$PWD/../../..
|
||||
PWD_ROOT_DIR = $$PWD
|
||||
include(../../base.pri)
|
||||
|
||||
ADD_DEPENDENCY(UnicodeConverter, kernel, cfcpp)
|
||||
|
||||
INCLUDEPATH += $$PWD/../
|
||||
|
||||
HEADERS += \
|
||||
tst_compondfile.h \
|
||||
tst_directoryentry.h \
|
||||
tst_header.h \
|
||||
tst_streamrw.h
|
||||
|
||||
SOURCES += \
|
||||
main.cpp
|
||||
37
Common/cfcpp/test/tst_compondfile.h
Normal file
37
Common/cfcpp/test/tst_compondfile.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
#include "compoundfile.h"
|
||||
#include "../../DesktopEditor/common/File.h"
|
||||
|
||||
using namespace testing;
|
||||
using namespace std;
|
||||
using namespace CFCPP;
|
||||
|
||||
|
||||
struct CompoundFileTest : testing::Test
|
||||
{
|
||||
wstring filename = L"../../../data/ex.ppt";
|
||||
CompoundFile cf;
|
||||
|
||||
CompoundFileTest() : cf(filename)
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CompoundFileTest, test_compoundfile_read)
|
||||
{
|
||||
EXPECT_TRUE(cf.HasSourceStream());
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CompoundFileTest, test_compoundfile_write)
|
||||
{
|
||||
wstring other_filename = L"../../../data/ex2.ppt";
|
||||
NSFile::CFileBinary::Remove(other_filename);
|
||||
cf.Save(other_filename);
|
||||
EXPECT_EQ(FileLenght(filename), FileLenght(other_filename));
|
||||
EXPECT_EQ(FileSimpleHash(filename), FileSimpleHash(other_filename));
|
||||
}
|
||||
76
Common/cfcpp/test/tst_directoryentry.h
Normal file
76
Common/cfcpp/test/tst_directoryentry.h
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
#include "directoryentry.h"
|
||||
#include "../../DesktopEditor/common/File.h"
|
||||
|
||||
using namespace testing;
|
||||
using namespace std;
|
||||
using namespace CFCPP;
|
||||
|
||||
|
||||
struct DirEntryTest : testing::Test
|
||||
{
|
||||
string filename;
|
||||
Stream stream;
|
||||
|
||||
DirEntryTest() :
|
||||
filename("../../../data/ex.ppt"),
|
||||
stream(OpenFileStream(filename))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void test_dirEntry_read(const DirectoryEntry& de)
|
||||
{
|
||||
EXPECT_EQ(de.GetEntryName(), L"Root Entry");
|
||||
EXPECT_EQ(de.getNameLength(), 22);
|
||||
EXPECT_EQ(de.getStgType(), StgRoot);
|
||||
|
||||
EXPECT_EQ(de.leftSibling, 0xFFFFFFFF);
|
||||
EXPECT_EQ(de.rightSibling, 0xFFFFFFFF);
|
||||
EXPECT_EQ(de.child, 1);
|
||||
|
||||
GUID storageCLSID;
|
||||
storageCLSID.Data1 = 0x64818D10;
|
||||
storageCLSID.Data2 = 0x4F9B;
|
||||
storageCLSID.Data3 = 0x11CF;
|
||||
storageCLSID.Data4 = 0xE829B900AA00EA86;
|
||||
// EXPECT_EQ(de.getStorageCLSID(), storageCLSID);
|
||||
|
||||
EXPECT_EQ(de.stateBits, 0);
|
||||
// todo
|
||||
//EXPECT_EQ(de.creationDate, 0);
|
||||
// EXPECT_EQ(de.modifyDate, 0xC0F1C03A18A1D801);
|
||||
EXPECT_EQ(de.startSetc, 3);
|
||||
EXPECT_EQ(de.size, 5632);
|
||||
}
|
||||
|
||||
TEST_F(DirEntryTest, test_directoryentry_read)
|
||||
{
|
||||
DirectoryEntry de(L"", StgInvalid, {});
|
||||
stream->seek(0x400, std::ios::beg);
|
||||
de.Read(stream);
|
||||
|
||||
EXPECT_EQ(stream->tell(), 0x480);
|
||||
test_dirEntry_read(de);
|
||||
}
|
||||
|
||||
TEST_F(DirEntryTest, test_directoryentry_write)
|
||||
{
|
||||
DirectoryEntry de(L"", StgInvalid, {});
|
||||
stream->seek(0x400, std::ios::beg);
|
||||
de.Read(stream);
|
||||
|
||||
std::string other_filename("../../../data/direntry.bin");
|
||||
stream = OpenFileStream(other_filename, true);
|
||||
de.Write(stream);
|
||||
EXPECT_EQ(stream->tell(), 0x80);
|
||||
stream->seek(0, std::ios::beg);
|
||||
|
||||
DirectoryEntry other(L"", StgInvalid, {});
|
||||
other.Read(stream);
|
||||
test_dirEntry_read(other);
|
||||
remove(other_filename.c_str());
|
||||
}
|
||||
110
Common/cfcpp/test/tst_header.h
Normal file
110
Common/cfcpp/test/tst_header.h
Normal file
@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
#include "header.h"
|
||||
#include "../../DesktopEditor/common/File.h"
|
||||
#include "streamrw.h"
|
||||
|
||||
using namespace testing;
|
||||
using namespace std;
|
||||
using namespace CFCPP;
|
||||
|
||||
|
||||
struct HeaderTest : testing::Test
|
||||
{
|
||||
string filename;
|
||||
Stream stream;
|
||||
Header hd;
|
||||
|
||||
HeaderTest() :
|
||||
filename("../../../data/ex.ppt"),
|
||||
stream(OpenFileStream(filename, false))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void test_header_state(const Header& hd)
|
||||
{
|
||||
ASSERT_THAT(hd.headerSignature, ElementsAre(0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1));
|
||||
EXPECT_EQ(hd.minorVersion, 0x003E);
|
||||
EXPECT_EQ(hd.majorVersion, 0x0003);
|
||||
EXPECT_EQ(hd.byteOrder, 0xFFFE);
|
||||
EXPECT_EQ(hd.sectorShift, 0x0009);
|
||||
EXPECT_EQ(hd.miniSectorShift, 0x0006);
|
||||
EXPECT_EQ(hd.directorySectorsNumber, 0);
|
||||
EXPECT_EQ(hd.fatSectorsNumber, 1);
|
||||
EXPECT_EQ(hd.firstDirectorySectorID, 1);
|
||||
EXPECT_EQ(hd.minSizeStandardStream, 0x1000);
|
||||
EXPECT_EQ(hd.firstMiniFATSectorID, 2);
|
||||
EXPECT_EQ(hd.miniFATSectorsNumber, 1);
|
||||
EXPECT_EQ(hd.firstDIFATSectorID, -2);
|
||||
EXPECT_EQ(hd.difatSectorsNumber, 0);
|
||||
|
||||
int difat[109];
|
||||
memset(reinterpret_cast<char*>(difat), 0xFF, sizeof(difat));
|
||||
difat[0] = 0;
|
||||
ASSERT_FALSE(memcmp(hd.difat, difat, sizeof(difat)));
|
||||
}
|
||||
|
||||
TEST_F(HeaderTest, test_header_open)
|
||||
{
|
||||
EXPECT_TRUE(IsOpen(stream));
|
||||
}
|
||||
|
||||
TEST_F(HeaderTest, test_header_read)
|
||||
{
|
||||
hd.Read(stream);
|
||||
test_header_state(hd);
|
||||
}
|
||||
|
||||
TEST_F(HeaderTest, test_header_write)
|
||||
{
|
||||
hd.Read(stream);
|
||||
|
||||
std::string other_filename("../../../data/header.bin");
|
||||
stream = OpenFileStream(other_filename, true);
|
||||
hd.Write(stream);
|
||||
|
||||
Header other;
|
||||
stream->seek(0, std::ios::beg);
|
||||
other.Read(stream);
|
||||
test_header_state(other);
|
||||
remove(other_filename.c_str());
|
||||
}
|
||||
|
||||
TEST_F(HeaderTest, test_header_seek)
|
||||
{
|
||||
hd.Read(stream);
|
||||
|
||||
std::string other_filename("../../../data/sheader.bin");
|
||||
remove(other_filename.c_str());
|
||||
stream = OpenFileStream(other_filename, true);
|
||||
|
||||
std::vector<char> zeroArray(512, 0);
|
||||
stream->write(zeroArray.data(), zeroArray.size());
|
||||
zeroArray.clear();
|
||||
|
||||
EXPECT_EQ(Length(stream), 512);
|
||||
|
||||
|
||||
stream->seek(4);
|
||||
StreamRW rw(stream);
|
||||
auto pos = rw.Tell();
|
||||
EXPECT_EQ(pos, 4);
|
||||
|
||||
hd.Write(stream);
|
||||
EXPECT_EQ(Length(stream), 512+4);
|
||||
|
||||
|
||||
Header other;
|
||||
stream->seek(0);
|
||||
int zeroValue = StreamRW(stream).Read<int>();
|
||||
EXPECT_EQ(zeroValue, 0);
|
||||
|
||||
|
||||
other.Read(stream);
|
||||
test_header_state(other);
|
||||
|
||||
|
||||
}
|
||||
78
Common/cfcpp/test/tst_streamrw.h
Normal file
78
Common/cfcpp/test/tst_streamrw.h
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
#include "streamrw.h"
|
||||
#include "../../DesktopEditor/common/File.h"
|
||||
#include <array>
|
||||
|
||||
using namespace testing;
|
||||
using namespace std;
|
||||
using namespace CFCPP;
|
||||
|
||||
struct StreamRWTest : testing::Test
|
||||
{
|
||||
string filename;
|
||||
Stream stream;
|
||||
shared_ptr<StreamRW> rw;
|
||||
const char symbol = 'a';
|
||||
const int integer = 13;
|
||||
|
||||
StreamRWTest() :
|
||||
filename("../../../data/types.bin"),
|
||||
stream(OpenFileStream(filename, true)),
|
||||
rw(new StreamRW(stream))
|
||||
{
|
||||
}
|
||||
|
||||
~StreamRWTest()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST_F(StreamRWTest, test_stream_open)
|
||||
{
|
||||
EXPECT_TRUE(IsOpen(stream));
|
||||
}
|
||||
|
||||
TEST_F(StreamRWTest, test_stream_write)
|
||||
{
|
||||
rw->Seek(0);
|
||||
rw->Write(symbol);
|
||||
rw->Write(integer);
|
||||
stream->flush();
|
||||
EXPECT_EQ((int)Length(stream), 5);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(StreamRWTest, test_stream_read)
|
||||
{
|
||||
EXPECT_EQ(rw->Seek(0), 0);
|
||||
EXPECT_EQ(rw->Read<char>(), symbol);
|
||||
EXPECT_EQ(rw->Read<int>(), integer);
|
||||
remove(filename.c_str());
|
||||
}
|
||||
|
||||
TEST_F(StreamRWTest, test_stream_rw_array)
|
||||
{
|
||||
int sarr[3] = {99, 0, -3};
|
||||
int darr[3] = {-1,-1,-1};
|
||||
rw->WriteArray(reinterpret_cast<char*>(sarr), sizeof (sarr));
|
||||
rw->Seek(0);
|
||||
rw->ReadArray(reinterpret_cast<char*>(darr), sizeof (darr));
|
||||
EXPECT_EQ(sarr[2], darr[2]);
|
||||
|
||||
remove(filename.c_str());
|
||||
}
|
||||
|
||||
TEST_F(StreamRWTest, test_stream_seek)
|
||||
{
|
||||
stream->write(std::array<char, 16>().data(), 16);
|
||||
stream->seek(2);
|
||||
int dataVal(1234567890);
|
||||
stream->write(reinterpret_cast<char*>(&dataVal), 4);
|
||||
EXPECT_EQ(Length(stream), 16);
|
||||
|
||||
remove(filename.c_str());
|
||||
}
|
||||
Reference in New Issue
Block a user