mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
250 lines
4.7 KiB
C++
250 lines
4.7 KiB
C++
#include "Core/Core.h"
|
|
|
|
#define LLOG(x)
|
|
|
|
namespace Upp {
|
|
|
|
namespace BinDiffDetail {
|
|
|
|
struct DataFinder {
|
|
const char *ptr;
|
|
const char *end;
|
|
const char *end8;
|
|
Index<uint64> pos;
|
|
|
|
int FindLongest(const char *s, const char *e, int& len) const;
|
|
|
|
DataFinder(const char *s, const char *e, size_t max_pos = 1024*1024);
|
|
};
|
|
|
|
DataFinder::DataFinder(const char *s, const char *e, size_t max_pos)
|
|
{
|
|
ptr = s;
|
|
end = e;
|
|
size_t len = e - s;
|
|
if(len < 8)
|
|
return;
|
|
const char *end8 = s + min(len - 8, max_pos);
|
|
for(const char *s = ptr; s < end8; s++)
|
|
pos.Add(Peek64(s));
|
|
}
|
|
|
|
int DataFinder::FindLongest(const char *s, const char *e, int& len) const
|
|
{
|
|
int found = 0;
|
|
len = 0;
|
|
|
|
if(e - s <= 8)
|
|
return 0;
|
|
|
|
int p = pos.Find(Peek64(s));
|
|
while(p >= 0 && len < e - s) {
|
|
const char *q = ptr + p;
|
|
if(end - q > len && q[len] == s[len]) {
|
|
q += 8;
|
|
const char *t = s + 8;
|
|
int l = (int)min(memeq8len(q, t, min(end - q, e - t)) + 8, (size_t)INT_MAX);
|
|
if(l > len) {
|
|
len = l;
|
|
found = p;
|
|
}
|
|
}
|
|
p = pos.FindNext(p);
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
}
|
|
|
|
String BinUndiff(const String& base, const String& bin)
|
|
{
|
|
String data;
|
|
int base_len = base.GetCount();
|
|
const byte *s = bin;
|
|
const byte *e = (const byte *)bin.End();
|
|
auto Get = [&] {
|
|
if(s < e) return *s++;
|
|
throw Exc();
|
|
};
|
|
auto GetInt = [&]() -> int {
|
|
byte h = Get();
|
|
byte l = Get();
|
|
if(h & 128) { // 32 bit
|
|
byte h0 = Get();
|
|
byte l0 = Get();
|
|
return MAKELONG(MAKEWORD(l0, h0), MAKEWORD(l, h & 127));
|
|
}
|
|
return MAKEWORD(l, h);
|
|
};
|
|
try {
|
|
while(s < e) {
|
|
byte b = *s++;
|
|
if(b == 0) {
|
|
int pos = GetInt();
|
|
int len = GetInt();
|
|
LLOG("Pos " << pos << ", len " << len);
|
|
if(pos >= 0 && pos < base_len && base_len - pos >= len)
|
|
data.Cat(base.Mid(pos, len));
|
|
else
|
|
return String::GetVoid();
|
|
}
|
|
else {
|
|
if(b <= e - s) {
|
|
data.Cat(s, b);
|
|
s += b;
|
|
}
|
|
else
|
|
return String::GetVoid();
|
|
}
|
|
}
|
|
}
|
|
catch(Exc) {
|
|
return String::GetVoid();
|
|
}
|
|
return data;
|
|
}
|
|
|
|
String BinDiff(const String& base, const String& data)
|
|
{
|
|
using namespace BinDiffDetail;
|
|
|
|
String diff;
|
|
const char *s = data;
|
|
const char *unmatched = s;
|
|
int unmatched_len = 0;
|
|
auto PutInt = [&](int n) {
|
|
LLOG("PutInt " << n);
|
|
if(n < 32768) {
|
|
diff.Cat(HIBYTE(n));
|
|
diff.Cat(LOBYTE(n));
|
|
}
|
|
else {
|
|
diff.Cat(HIBYTE(HIWORD(n)) | 128);
|
|
diff.Cat(LOBYTE(HIWORD(n)));
|
|
diff.Cat(HIBYTE(LOWORD(n)));
|
|
diff.Cat(LOBYTE(LOWORD(n)));
|
|
}
|
|
};
|
|
auto FlushUnmatched = [&] {
|
|
if(unmatched_len) {
|
|
LLOG("Unmatched " << String(unmatched, unmatched_len));
|
|
diff.Cat(unmatched_len);
|
|
diff.Cat(unmatched, unmatched_len);
|
|
unmatched_len = 0;
|
|
}
|
|
};
|
|
DataFinder f(~base, base.end());
|
|
while(s < data.end()) {
|
|
int matchlen = 0;
|
|
int foundpos = 0;
|
|
foundpos = f.FindLongest(s, data.end(), matchlen);
|
|
if(matchlen < 8) {
|
|
int l = max(matchlen, 1);
|
|
if(l + unmatched_len > 255) {
|
|
FlushUnmatched();
|
|
unmatched_len = l;
|
|
unmatched = s;
|
|
s += l;
|
|
}
|
|
else {
|
|
unmatched_len += l;
|
|
s += l;
|
|
}
|
|
}
|
|
else {
|
|
FlushUnmatched();
|
|
diff.Cat(0); // use data from base
|
|
PutInt(foundpos);
|
|
PutInt(matchlen);
|
|
s += matchlen;
|
|
unmatched = s;
|
|
}
|
|
}
|
|
FlushUnmatched();
|
|
return diff;
|
|
}
|
|
|
|
void BinUndoRedo::Reset(const String& current, const Value& info)
|
|
{
|
|
undo.Clear();
|
|
redo.Clear();
|
|
undosize = 0;
|
|
commit.data = current;
|
|
commit.info = info;
|
|
}
|
|
|
|
bool BinUndoRedo::DropUndo()
|
|
{
|
|
if(undo.GetCount()) {
|
|
undosize -= undo[0].data.GetCount();
|
|
undo.Remove(0);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BinUndoRedo::DropRedo()
|
|
{
|
|
if(redo.GetCount()) {
|
|
redo.Remove(0);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BinUndoRedo::Commit(const String& current, const Value& info, int limit)
|
|
{
|
|
bool ret = false;
|
|
if(redo.GetCount()) {
|
|
redo.Clear();
|
|
ret = true;
|
|
}
|
|
if(current != commit.data || info != commit.info) {
|
|
String u = BinDiff(current, commit.data);
|
|
while(undosize + u.GetCount() > limit)
|
|
if(!DropUndo())
|
|
break;
|
|
Entry& e = undo.Add();
|
|
e.data = u;
|
|
e.info = commit.info;
|
|
undosize += u.GetCount();
|
|
undosize += info.GetCount();
|
|
commit.data = current;
|
|
commit.info = info;
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
String BinUndoRedo::Undo(const String& current)
|
|
{
|
|
if(undo.GetCount()) {
|
|
Entry prev = undo.Pop();
|
|
commit.data = BinUndiff(commit.data, prev.data);
|
|
commit.info = prev.info;
|
|
|
|
Entry& e = redo.Add();
|
|
e.data = BinDiff(commit.data, current);
|
|
e.info = prev.info;
|
|
return commit.data;
|
|
}
|
|
return String::GetVoid();
|
|
}
|
|
|
|
String BinUndoRedo::Redo(const String& current)
|
|
{
|
|
if(redo.GetCount()) {
|
|
Entry next = redo.Pop();
|
|
commit.data = BinUndiff(commit.data, next.data);
|
|
commit.info = next.info;
|
|
|
|
Entry& e = undo.Add();
|
|
e.data = BinDiff(commit.data, current);
|
|
e.info = next.info;
|
|
return commit.data;
|
|
}
|
|
return String::GetVoid();
|
|
}
|
|
|
|
}
|