Overview
Examples
Screenshots
Comparisons
Applications
Download
Documentation
Tutorials
UppHub
Status & Roadmap
FAQ
Authors & License
Forums
Funding U++
Search on this site











SourceForge.net Logo

SourceForge.net Logo

GitHub Logo

Discord Logo

CompDir

 

Compares directory contents

 

 

 

textdiff.h

 

#ifndef _CompDir_textdiff_h_

#define _CompDir_textdiff_h_

 

class TextSection

{

public:

    TextSection(int start1, int count1, int start2, int count2, bool same)

        : start1(start1), count1(count1), start2(start2), count2(count2), same(same) {}

 

public:

    int      start1;

    int      count1;

    int      start2;

    int      count2 : 31;

    unsigned same   : 1;

};

 

Array<TextSection> CompareLineMaps(const Vector<String>& l1, const Vector<String>& l2);

Vector<String>     GetLineMap(Stream& stream);

Vector<String>     GetFileLineMap(const String& path);

Vector<String>     GetStringLineMap(const String &s);

 

#endif

 

 

 

CompDir.h

 

#ifndef _CompDir_CompDir_h

#define _CompDir_CompDir_h

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

#include "textdiff.h"

 

#endif

 

 

 

main.cpp

 

#include "CompDir.h"

#pragma hdrstop

 

#define LAYOUTFILE <CompDir/CompDir.lay>

#include <CtrlCore/lay.h>

 

#define IMAGEFILE  <CompDir/CompDir.iml>

#define IMAGECLASS CompDirImg

#include <Draw/iml.h>

 

String NormalizePathCase(String fn)

{

#ifdef PLATFORM_WIN32 // !PATH_CASE

    return ToLower(fn);

#else

    return fn;

#endif

}

 

static String ExpandTabs(String line, int tabsize = 4)

{

    String out;

    int pos = 0;

    for(const char *p = line; *p; p++)

        if(*p == '\t') {

            int left = tabsize - pos % tabsize;

            out.Cat(' ', left);

            pos += left;

        }

        else {

            out.Cat(*p);

            pos++;

        }

    return out;

}

 

class DlgCompareDir : public WithCompareDirLayout<TopWindow> {

public:

    typedef DlgCompareDir CLASSNAME;

    DlgCompareDir();

 

    void Run();

 

    void Serialize(Stream& stream);

 

private:

    void CmdRefresh();

    void DoTreeCursor();

    int  Refresh(String rel_path, int parent);

    void DoBrowse(Ctrl *field);

    void ToolTree(Bar& bar);

    String GetTreePath() const;

 

private:

    struct FileInfo : Moveable<FileInfo>

    {

        FileInfo() {}

        FileInfo(String name, int64 size, Time time) : name(name), size(size), time(time) {}

 

        String name;

        int64  size;

        Time   time;

    };

 

    bool FetchDir(String dir, VectorMap<String, FileInfo>& files, VectorMap<String, String>& dirs);

 

    FrameRight<Button> browse_a, browse_b;

    TreeCtrl tree;

    StaticRect editor;

    LineEdit lineedit;

    RichTextCtrl qtf;

    String pa, pb, fm;

};

 

DlgCompareDir::DlgCompareDir()

{

    CtrlLayout(*this, "Compare directories");

    Sizeable().Zoomable();

    refresh <<= THISBACK(CmdRefresh);

    splitter.Vert(tree, editor);

    editor << lineedit.SizePos() << qtf.SizePos();

    qtf.Background(White());

    qtf.SetFrame(InsetFrame());

    path_a.AddFrame(browse_a);

    browse_a.SetImage(CtrlImg::right_arrow());

    browse_a <<= THISBACK1(DoBrowse, &path_a);

    path_b.AddFrame(browse_b);

    browse_b.SetImage(CtrlImg::right_arrow());

    browse_b <<= THISBACK1(DoBrowse, &path_b);

    file_mask <<= "*.cpp *.h *.hpp *.c *.C *.cxx *.cc *.lay *.iml *.upp *.sch *.dph";

    tree.WhenCursor = THISBACK(DoTreeCursor);

    lineedit.SetReadOnly();

    lineedit.SetFont(Courier(14));

}

 

void DlgCompareDir::Run()

{

    TopWindow::Run();

}

 

void DlgCompareDir::Serialize(Stream& stream)

{

    int version = 1;

    stream / version;

    stream % path_a % path_b % file_mask;

    SerializePlacement(stream);

    stream % splitter;

}

 

void DlgCompareDir::CmdRefresh()

{

    pa = ~path_a;

    pb = ~path_b;

    fm = ~file_mask;

    tree.Clear();

    Image icon;

    switch(Refresh(Null, 0)) {

    case 0: icon = CtrlImg::Dir(); break;

    case 1: icon = CompDirImg::a_dir(); break;

    case 2: icon = CompDirImg::b_dir(); break;

    case 3: icon = CompDirImg::ab_dir(); break;

    }

    tree.SetRoot(icon, "Root");

}

 

bool DlgCompareDir::FetchDir(String dir, VectorMap<String, FileInfo>& files, VectorMap<String, String>& dirs)

{

    FindFile ff;

    if(!ff.Search(AppendFileName(dir, "*")))

        return false;

    do

        if(ff.IsFile() && PatternMatchMulti(fm, ff.GetName()))

            files.Add(NormalizePathCase(ff.GetName()), FileInfo(ff.GetName(), ff.GetLength(), ff.GetLastWriteTime()));

        else if(ff.IsFolder())

            dirs.Add(NormalizePathCase(ff.GetName()), ff.GetName());

    while(ff.Next());

    return true;

}

 

int DlgCompareDir::Refresh(String rel_path, int parent)

{

    FindFile ff;

    VectorMap<String, FileInfo> afile, bfile;

    VectorMap<String, String> adir, bdir;

    String arel = AppendFileName(pa, rel_path);

    String brel = AppendFileName(pb, rel_path);

    int done = 0;

    if(!FetchDir(arel, afile, adir))

        done |= 2;

    if(!FetchDir(brel, bfile, bdir))

        done |= 1;

 

    Index<String> dir_index;

    dir_index <<= adir.GetIndex();

    FindAppend(dir_index, bdir.GetKeys());

    Vector<String> dirs(dir_index.PickKeys());

    Sort(dirs, GetLanguageInfo());

    for(int i = 0; i < dirs.GetCount(); i++) {

        int fa = adir.Find(dirs[i]), fb = bdir.Find(dirs[i]);

        String dn = (fb >= 0 ? bdir[fb] : adir[fa]);

        int dirpar = tree.Add(parent, CtrlImg::Dir(), dn);

        int dirdone = Refresh(AppendFileName(rel_path, dirs[i]), dirpar);

        done |= dirdone;

        switch(dirdone) {

        case 0: tree.Remove(dirpar); break;

        case 1: tree.SetNode(dirpar, TreeCtrl::Node().SetImage(CompDirImg::a_dir()).Set(dn)); break;

        case 2: tree.SetNode(dirpar, TreeCtrl::Node().SetImage(CompDirImg::b_dir()).Set(dn)); break;

        case 3: tree.SetNode(dirpar, TreeCtrl::Node().SetImage(CompDirImg::ab_dir()).Set(dn)); break;

        }

    }

    Index<String> name_index;

    name_index <<= afile.GetIndex();

    FindAppend(name_index, bfile.GetKeys());

    Vector<String> names(name_index.PickKeys());

    Sort(names, GetLanguageInfo());

    for(int i = 0; i < names.GetCount(); i++) {

        int fa = afile.Find(names[i]), fb = bfile.Find(names[i]);

        if(fa < 0) {

            tree.Add(parent, CompDirImg::b_file(), NFormat("%s: B (%`, %0n)", bfile[fb].name, bfile[fb].time, bfile[fb].size));

            done |= 2;

        }

        else if(fb < 0) {

            tree.Add(parent, CompDirImg::a_file(), NFormat("%s: A (%`, %0n)", afile[fa].name, afile[fa].time, afile[fa].size));

            done |= 1;

        }

        else if(afile[fa].size != bfile[fb].size

        || LoadFile(AppendFileName(arel, names[i])) != LoadFile(AppendFileName(brel, names[i]))) {

            tree.Add(parent, CompDirImg::ab_file(), NFormat("%s: A (%`, %0n), B (%`, %0n)",

                bfile[fb].name, afile[fa].time, afile[fa].size, bfile[fb].time, bfile[fb].size));

            done |= 3;

        }

    }

    return done;

}

 

String DlgCompareDir::GetTreePath() const

{

    int i = tree.GetCursor();

    if(i < 0)

        return String::GetVoid();

    if(i == 0)

        return Null;

    String s = tree.Get(i);

    int f = s.Find(':');

    if(f >= 0)

        s.Trim(f);

    while((i = tree.GetParent(i)) != 0)

        s = AppendFileName(String(tree.Get(i)), s);

    return s;

}

 

void DlgCompareDir::DoTreeCursor()

{

    String s = GetTreePath();

    if(IsNull(s))

        return;

    String fa = AppendFileName(pa, s), fb = AppendFileName(pb, s);

    String da = LoadFile(fa), db = LoadFile(fb);

    if(!IsNull(da) || !IsNull(db)) {

        if(IsNull(da) || IsNull(db)) {

            qtf.Hide();

            lineedit.Show();

            lineedit <<= Nvl(db, da);

        }

        else {

            lineedit.Hide();

            qtf.Show();

            String comptext = "[C2 ";

            Vector<String> la = GetStringLineMap(da), lb = GetStringLineMap(db);

            Array<TextSection> sections = CompareLineMaps(la, lb);

            for(int s = 0; s < sections.GetCount(); s++) {

                const TextSection& sec = sections[s];

                if(sec.same) {

                    comptext << "[@(0.0.0) \1";

                    if(sec.count1 <= 6)

                        for(int i = 0; i < sec.count1; i++)

                            comptext << ExpandTabs(la[i + sec.start1]) << '\n';

                    else {

                        for(int i = 0; i < 3; i++)

                            comptext << ExpandTabs(la[i + sec.start1]) << '\n';

                        comptext << "...\n";

                        for(int i = -3; i < 0; i++)

                            comptext << ExpandTabs(la[i + sec.start1 + sec.count1]) << '\n';

                    }

                    comptext << "\1]";

                }

                else {

                    if(sec.count1) {

                        comptext << "[@(0.160.0) \1";

                        for(int i = 0; i < sec.count1; i++)

                            comptext << ExpandTabs(la[sec.start1 + i]) << '\n';

                        comptext << "\1]";

                    }

                    if(sec.count2) {

                        comptext << "[@(0.0.255) \1";

                        for(int i = 0; i < sec.count2; i++)

                            comptext << ExpandTabs(lb[sec.start2 + i]) << '\n';

                        comptext << "\1]";

                    }

                }

            }

            qtf.SetQTF(comptext);

        }

    }

}

 

void DlgCompareDir::DoBrowse(Ctrl *field)

{

    FileSel fsel;

    fsel.AllFilesType();

    static String recent_dir;

    fsel <<= Nvl((String)~*field, recent_dir);

    if(fsel.ExecuteSelectDir())

        *field <<= recent_dir = ~fsel;

}

 

void DlgCompareDir::ToolTree(Bar& bar)

{

}

 

GUI_APP_MAIN

{

    DlgCompareDir cmpdlg;

    LoadFromFile(cmpdlg, ConfigFile());

    cmpdlg.Run();

    StoreToFile(cmpdlg, ConfigFile());

}

 

 

 

textdiff.cpp

 

#include "CompDir.h"

 

template <class I>

static int CompareGetCount(I a, I b, int max_count)

{

    if(max_count <= 0 || *a != *b)

        return 0;

    int left;

    for(left = max_count; --left > 0;)

        if(*++a != *++b)

            return max_count - left;

    return max_count;

}

 

Vector<String> GetLineMap(Stream& stream)

{

    Vector<String> out;

    int emp = 0;

    if(stream.IsOpen())

        while(!stream.IsEof()) {

            String s = stream.GetLine();

            const char *p = s, *e = s.End(), *f = e;

            while(e > p && (byte)e[-1] <= ' ')

                e--;

            if(e == p)

                emp++;

            else

            {

                while(emp-- > 0)

                    out.Add(Null);

                if(e != f)

                    s.Trim(e - p);

                out.Add(s);

                emp = 0;

            }

        }

    return out;

}

 

Vector<String> GetFileLineMap(const String& path)

{

    FileIn fi(path);

    return GetLineMap(fi);

}

 

Vector<String> GetStringLineMap(const String& s)

{

    StringStream ss(s);

    return GetLineMap(ss);

}

 

class TextComparator

{

public:

    TextComparator(const Vector<String>& f1, const Vector<String>& f2);

 

    Array<TextSection>    GetSections() const;

 

private:

    bool                  Find(int start1, int end1, int start2, int end2, int& best_match, int& best_count) const;

    void                  Split(Array<TextSection>& dest, int start1, int end1, int start2, int end2) const;

 

private:

    Vector<Index<dword>>  hash1;

    Vector<Index<dword>>  hash2;

    const Vector<String>& file1;

    const Vector<String>& file2;

};

 

Array<TextSection> CompareLineMaps(const Vector<String>& s1, const Vector<String>& s2)

{

    return TextComparator(s1, s2).GetSections();

}

 

static void CalcHash(Vector<Index<dword>>& hash, const Vector<String>& file, int limit)

{

    { // 1st row

        Index<dword>& first = hash.Add();

        for(int i = 0; i < file.GetCount(); i++)

            first.Add(GetHashValue(file[i]));

    }

    static const int prime[] =

    {

        3,  5,  7,   11,  13,  17,  19,  21,

        23, 29, 31,  37,  41,  43,  47,  51,

        53, 61, 67,  71,  73,  79,  83,  87,

        89, 97, 101, 103, 107, 109, 113, 117,

    };

    const int *pp = prime;

    for(int l = 1; l < limit; l <<= 1) {

        Index<dword>& nhash = hash.Add();

        const Index<dword>& ohash = hash[hash.GetCount() - 2];

        int pri = *pp++;

        int t;

        for(t = l; t < ohash.GetCount(); t++)

            nhash.Add(ohash[t - l] + pri * ohash[t]);

        for(t -= l; t < ohash.GetCount(); t++)

            nhash.Add(ohash[t]);

    }

}

 

TextComparator::TextComparator(const Vector<String>& f1, const Vector<String>& f2)

: file1(f1), file2(f2)

{

    int limit = min(f1.GetCount(), f2.GetCount());

    CalcHash(hash1, f1, limit);

    CalcHash(hash2, f2, limit);

}

 

static bool CompareSection(const TextSection& ta, const TextSection& tb)

{

    return ta.start1 < tb.start1 || ta.start1 == tb.start1 && ta.start2 < tb.start2;

}

 

Array<TextSection> TextComparator::GetSections() const

{

    Array<TextSection> output;

    Split(output, 0, file1.GetCount(), 0, file2.GetCount());

    Sort(output, &CompareSection);

    return output;

}

 

static int GetHashLevel(int min_count, int hash_count)

{

    int l = 0;

    hash_count--;

    while(min_count > 1 && l < hash_count)

    {

        min_count >>= 1;

        l++;

    }

    return l;

}

 

bool TextComparator::Find(int start1, int end1, int start2, int end2, int& best_match, int& best_count) const

{

    ASSERT(end1 > start1 && end2 > start2);

    bool done = false;

    const String *f1 = file1.Begin() + start1;

    int len1 = end1 - start1;

    int lvl = GetHashLevel(best_count + 1, hash1.GetCount());

    int chunk = 1 << lvl;

    int last = max(best_count - chunk + 1, 0);

    const Index<dword> *hp1 = &hash1[lvl];

    const Index<dword> *hp2 = &hash2[lvl];

    const dword *h1 = hp1->begin() + start1;

 

    int i = hp2->Find(*h1);

    while(i >= 0)

        if(i + best_count >= end2)

            return done;

        else {

            if(i >= start2 && h1[last] == (*hp2)[i + last]) {

                int top = min(len1, end2 - i);

                int hc = CompareGetCount(h1, hp2->begin() + i, top) + chunk - 1;

                int cnt = CompareGetCount(f1, file2.begin() + i, min(hc, top));

                if(cnt > best_count) {

                    best_count = cnt;

                    best_match = i;

                    done = true;

                    last = best_count - chunk + 1;

                    if(best_count + 1 >= 2 * chunk)

                    {

                        lvl = GetHashLevel(best_count + 1, hash1.GetCount());

                        chunk = 1 << lvl;

                        last = best_count - chunk + 1;

                        hp1 = &hash1[lvl];

                        hp2 = &hash2[lvl];

                        h1 = hp1->begin() + start1;

                        int oi = i;

                        for(i = hp2->Find(*h1); i >= 0 && i <= oi; i = hp2->FindNext(i))

                            ;

                        continue;

                    }

                }

            }

            i = hp2->FindNext(i);

        }

    return done;

}

 

void TextComparator::Split(Array<TextSection>& dest, int start1, int end1, int start2, int end2) const

{

    ASSERT(start1 <= end1 && start2 <= end2);

    while(start1 < end1 && start2 < end2) {

        int new1 = -1, new2 = -1, count = 0;

        for(int i = start1; i + count < end1; i++)

            if(Find(i, end1, start2, end2, new2, count))

                new1 = i;

        if(count == 0)

            break; // no match at all

        ASSERT(new1 >= start1 && new1 + count <= end1);

        ASSERT(new2 >= start2 && new2 + count <= end2);

        dest.Add(TextSection(new1, count, new2, count, true));

        if(new1 - start1 >= end1 - new1 - count) { // head is longer - recurse for tail

            Split(dest, new1 + count, end1, new2 + count, end2);

            end1 = new1;

            end2 = new2;

        }

        else { // tail is longer - recurse for head

            Split(dest, start1, new1, start2, new2);

            start1 = new1 + count;

            start2 = new2 + count;

        }

        ASSERT(start1 <= end1 && start2 <= end2);

    }

    if(start1 < end1 || start2 < end2)

        dest.Add(TextSection(start1, end1 - start1, start2, end2 - start2, false));

}

 

 

 

CompDir.lay

 

LAYOUT(CompareDirLayout, 496, 492)

    ITEM(Label, dv___0, SetLabel(t_("Folder &A:")).LeftPosZ(4, 52).TopPosZ(4, 19))

    ITEM(EditField, path_a, HSizePosZ(56, 4).TopPosZ(4, 19))

    ITEM(Label, dv___2, SetLabel(t_("Folder &B:")).LeftPosZ(4, 52).TopPosZ(26, 19))

    ITEM(EditField, path_b, HSizePosZ(56, 4).TopPosZ(26, 19))

    ITEM(Label, dv___4, SetLabel(t_("File &mask:")).LeftPosZ(4, 52).TopPosZ(48, 19))

    ITEM(EditField, file_mask, HSizePosZ(56, 68).TopPosZ(48, 19))

    ITEM(Button, refresh, SetLabel(t_("&Refresh")).RightPosZ(4, 60).TopPosZ(48, 18))

    ITEM(Splitter, splitter, HSizePosZ(4, 4).VSizePosZ(70, 4))

END_LAYOUT

 

 

 

 

 

Do you want to contribute?