jContinuousBackup : Continuous backup of directories

Download jcontinuousbackup.zip

Synopsis:

jContinuousBackup.cfg
CompareDirTrees.cpp
CompareDirTrees.h
ConfigFile.cpp
ConfigFile.h
ConfigParser.cpp
ConfigParser.h
DirInfo.h
FileSystem.cpp
FileSystem.h
globals.cpp
globals.h
jContinuousBackup.cpp
Notification.cpp
Notification.h
NotificationHandles.cpp
NotificationHandles.h


jContinuousBackup.cfg

Synopsis
#hey true blue

    d:\projects\src\junk
d:\projects\test\backup\junk      
true


    #------------
d:\projects\src\alphacalc
d:\projects\test\backup\alphacalc
false


#-- has embedded white space
    C:\My Installations   
d:\projects\test\backup\installs
true
  

CompareDirTrees.cpp

Synopsis
#include <string>
#include <algorithm>
#include <stack>
using namespace std;

#include <ctype.h>

#include "globals.h"
#include "CompareDirTrees.h"
#include "FileSystem.h"

class DirsToDo
  {
  public:
    void Init()
      {
      mDirsToDo.push(".");
      }
    bool More()
      {
      return !mDirsToDo.empty();
      }
    string Next()
      {
      string dir = mDirsToDo.top();
      mDirsToDo.pop();
      return dir;
      }
    void PushAllSubDirs(const string& root, const string& dir)
      {
      FileSystem::PathList dirlist = FileSystem::GetDirs(dir == "" ? root : root + "\\" + dir);
      for(FileSystem::PathList::iterator it = dirlist.begin(); it != dirlist.end(); ++it)
        mDirsToDo.push(dir == "" ? *it : dir + "\\" + *it);
      }

  private:
    stack<string> mDirsToDo;
  } ;

class Dirs
  {
  public:
    Dirs(const string& dir1, const string& dir2, CompareDirTrees::Functor& fn)
      : masterroot(dir1), slaveroot(dir2), mCallback(fn)
      {
      }
    void Compare()
      {
      masterfiles = FileSystem::GetFiles(masterroot);
      slavefiles = FileSystem::GetFiles(slaveroot);
      CompareFileLists();
      }
  private:
    void CompareFileLists()
      {
      for(FileSystem::PathList::const_iterator it = masterfiles.begin(); it != masterfiles.end(); ++it)
        CompareFile(*it);
      }
    void CompareFile(const string& file)
      {
      if (SameFile(file)) return;
      mCallback(masterroot, slaveroot, file);
      }
    bool SameFile(const string& file)
      {
      return FileInBoth(file) && FilesAreEqual(file);
      }
    bool FileInBoth(const string& file)
      {
      //must compare case-insensitive
      FileSystem::PathList::const_iterator it;
      for(it = slavefiles.begin(); it != slavefiles.end(); ++it)
        {
        if (ToLower(*it) == ToLower(file)) 
          break;
        }

      if (it != slavefiles.end())
        return true;

      globals.Log << now 
         << "Found new file: " + file 
         << endl;
      return false;
      }
    bool FilesAreEqual(const string& file)
      {
      return FileSystem::FilesAreEqual(masterroot, slaveroot, file);
      }
    string ToLower(const string& s)
      {
      string r = s;
      transform(r.begin(), r.end(), r.begin(), ::tolower);
      return r;
      }
    FileSystem::PathList masterfiles;
    FileSystem::PathList slavefiles;

    string masterroot;
    string slaveroot;
    CompareDirTrees::Functor& mCallback;
  } ;

class CompareDirTrees::Private
  {
  public:
    void Init(const string& masterdir, const string& slavedir)
      {
      masterroot = masterdir;
      slaveroot = slavedir;
      masterrelativeroot = "";
      slaverelativeroot = "";
      }
    void AddNextNode(const string& dir)
      {
      if (dir == ".") return;
      masterrelativeroot = dir;
      slaverelativeroot = dir;
      }
    void CreateSlaveDir()
      {
      if (FileSystem::DirExists(SlavePath())) return;
      FileSystem::CreateDir(SlavePath());
      }

    void CompareDirs(Functor& fn)
      {
      Dirs dirs(MasterPath(), SlavePath(), fn);
      dirs.Compare();
      }

    string masterrelativeroot;
    string slaverelativeroot;

  private:
    string MasterPath()
      {
      return masterroot + "\\" + masterrelativeroot;
      }
    string SlavePath()
      {
      return slaveroot + "\\" + slaverelativeroot;
      }
  private:
    string masterroot;
    string slaveroot;
  } ;

CompareDirTrees::CompareDirTrees()
: impl(* new CompareDirTrees::Private)
  {
  }
CompareDirTrees::~CompareDirTrees()
  {
  delete &impl;
  }
void CompareDirTrees::Compare(const string& masterdir, const string& slavedir, bool subdirs, Functor& fn)
  {
  impl.Init(masterdir, slavedir);

  DirsToDo dirs;
  dirs.Init();
  while (dirs.More())
    {
    string dir = dirs.Next();

    impl.AddNextNode(dir);
    impl.CreateSlaveDir();
    impl.CompareDirs(fn);

    if (subdirs)
      dirs.PushAllSubDirs(masterdir, impl.masterrelativeroot);
    }
  }

CompareDirTrees.h

Synopsis
#include <string>
using std::string;

class CompareDirTrees
  {
  public:
    struct Functor
      {
      virtual void operator() (const string& masterdir, const string& slavedir, const string& filename) = 0;
      } ;

    CompareDirTrees();
    ~CompareDirTrees();
    void Compare(const string& masterdir, const string& slavedir, bool subdirs, Functor& fn);

  private:
    class Private;
    Private& impl;
  } ;

ConfigFile.cpp

Synopsis
#include "globals.h"
#include "ConfigParser.h"
#include "ConfigFile.h"
#include "Notification.h"

ConfigFile::ConfigFile(Notification& notify)
: mNotify(notify)
  {
  }

void ConfigFile::operator() (const ConfigParser::ValueList& values)
  {
  globals.Log << now 
              << "dir=" << values[0]
              << " backupdir=" << values[1]
              << " subdirs=" << values[2]
              << endl;

  mNotify.Add(values[0], 
            (values[2] == "true") ? true : false, 
            values[1]);
  }

void ConfigFile::Load()
  {
  ConfigParser parser(string("jContinuousBackup.cfg"), 3);
  parser.parse(*this);
  }

ConfigFile.h

Synopsis
#pragma once

#include "ConfigParser.h"

class Notification;
class ConfigFile : public ConfigParser::Functor
  {
  public:
    ConfigFile(Notification& notify);
    void operator() (const ConfigParser::ValueList& values);
    void Load();

  private:
    Notification& mNotify;
  } ;

ConfigParser.cpp

Synopsis
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
using namespace std;

#include "ConfigParser.h"

class ConfigParser::Private
  {
  public:
    Private(const string& fname, int numitems)
      : mState(0), mNumLinesPerItem(numitems), mFilename(fname)
      {
      }
    void Reset()
      {
      mState = 0;
      mValues.clear();
      }
    bool More()
      {
      if (mFile.eof()) return false;
      GetLine();
      return true;
      }
    bool OkToCallback()
      {
      return mState == mNumLinesPerItem;
      }
    void Close()
      {
      if (!mFile.is_open()) return;
      mFile.close();
      }
    void Open()
      {
      if (mFile.is_open()) return;
      mFile.open(mFilename.c_str());
      //mFile.ipfx(false);
      }

    ValueList mValues;

  private:
    bool IsIgnoredLine(const string& val)
      {
      unsigned posn = val.find_first_not_of(" \t\r");
      return posn == string::npos || val[posn] == '#';
      }
    void SaveLine(const string& val)
      {
      mValues.push_back(Trim(val));
      mState++;
      }
    string Trim(const string& val)
      {
      size_t start = val.find_first_not_of(" \t\r");
      size_t end = val.find_last_not_of(" \t\r");
      return val.substr(start, end - start + 1);
      }
    void GetLine()
      {
      string val;
      getline(mFile, val);
      if (IsIgnoredLine(val)) return;
      SaveLine(val);
      }

    int mState;
    int mNumLinesPerItem;
    string mFilename;

    string mValue;
    ifstream mFile;
  } ;

ConfigParser::ConfigParser(const string& fname, int numitems)
: impl(* new ConfigParser::Private(fname, numitems))
  {
  impl.Open();
  }

ConfigParser::~ConfigParser()
  {
  impl.Close();
  delete &impl;
  }

void ConfigParser::parse(Functor& callback)
  {
  impl.Reset();
  while(impl.More())
    {
    if (!impl.OkToCallback()) continue;

    callback(impl.mValues);

    impl.Reset();
    }
  impl.Close();
  }


ConfigParser.h

Synopsis
#pragma once

#include <vector>
#include <string>
using std::string;
using std::vector;

class ConfigParser
  {
  public:
    typedef vector<string> ValueList;
    struct Functor
      {
      virtual void operator() (const ValueList& values) = 0;
      } ;

    ConfigParser(const string& fname, int numitems);
    ~ConfigParser();
    void parse(Functor& callback);

  private:
    class Private;
    Private& impl;
  };

DirInfo.h

Synopsis
#pragma once

#include <string>
using std::string;

struct DirInfo
  {
  bool   subdirs;
  string dir;
  string backupdir;
  } ;

FileSystem.cpp

Synopsis
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <fstream>
using std::ifstream;

#include "globals.h"
#include "FileSystem.h"

void FileSystem::CreateDir(const string& dir)
  {
  if (DirExists(dir))
    return;

  //for each node in the path create the directory
  int index = -1;
  for(;;)
    {
    index = dir.find('\\', index + 1);
    if ((unsigned)index == string::npos)
      break;

    string p = dir.substr(0, index);
    if (!DirExists(p))
      ::CreateDirectory(p.c_str(), 0);
    }
  if (!DirExists(dir))
    ::CreateDirectory(dir.c_str(), 0);

  }
bool FileSystem::DirExists(const string& dir)
  {
  DWORD res = ::GetFileAttributes(dir.c_str());
  if (res == 0xFFFFFFFF) return false;
  if (res & FILE_ATTRIBUTE_DIRECTORY) return true;
  return false;
  }

FileSystem::PathList FileSystem::GetDirs(const string& dir)
  {
  PathList subdirlist;

  WIN32_FIND_DATA fdata;

  string fspec = dir + "\\*.*";
  HANDLE hfile = FindFirstFile(fspec.c_str(), &fdata);

  if (hfile == INVALID_HANDLE_VALUE)
    return subdirlist;

  for (BOOL rc = TRUE; rc; rc = FindNextFile(hfile, &fdata))
    {
    string f= fdata.cFileName;
    if (f == string(".") || f == string(".."))
      continue;

    if ((fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
      continue;

    subdirlist.push_back(f);
    }

  FindClose(hfile);
  return subdirlist;
  }

FileSystem::PathList FileSystem::GetFiles(const string& rootdir)
  {
  PathList filelist;

  WIN32_FIND_DATA fdata;

  string fspec = rootdir + "\\*.*";
  HANDLE hfile = FindFirstFile(fspec.c_str(), &fdata);

  if (hfile == INVALID_HANDLE_VALUE)
    return filelist;

  for (BOOL rc = TRUE; rc; rc = FindNextFile(hfile, &fdata))
    {
    string f= fdata.cFileName;
    if (f == string(".") || f == string(".."))
      continue;

    if ((fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
      continue;

    filelist.push_back(f);
    }

  FindClose(hfile);
  return filelist;
  }

bool FileSystem::FilesAreEqual(const string& masterdir, const string& slavedir, const string& fname)
  {
  unsigned long masterhash = GetHash(masterdir + "\\" + fname);
  unsigned long slavehash = GetHash(slavedir + "\\" + fname);
  if (masterhash == slavehash)
    return true;

  globals.Log << now 
      << "file=" << masterdir + "\\" + fname 
      << " has changed: masterhash=" << masterhash
      << " slavehash=" << slavehash
      << endl;

  return false;
  }

unsigned long FileSystem::GetHash(const string& path)
  {
  ifstream f(path.c_str());
  if (!f.is_open())
    return 0xFFFFFFFF;

  unsigned long hash = 0;
  while(!f.eof())
    {
    unsigned long buf[120];
    memset(buf, 0, sizeof(buf));
    f.read((char*) buf, sizeof(buf));
    for (int i = 0; i < 120; ++i)
      hash ^= buf[i];
    }
  return hash;
  }

FileSystem.h

Synopsis
#pragma once

#include <string>
#include <vector>
using std::string;
using std::vector;

struct FileSystem
  {
  static void CreateDir(const string& dir);
  static bool DirExists(const string& dir);

  typedef vector<string> PathList;
  static PathList GetDirs(const string& dir);
  static PathList GetFiles(const string& rootdir);
  static bool FilesAreEqual(const string& masterdir, const string& slavedir, const string& fname);
  static unsigned long GetHash(const string& path);
  } ;

globals.cpp

Synopsis
#include <fstream>
using namespace std;
#include <time.h>
#include "globals.h"

Globals globals;

Globals::Globals()
: Log("jContinuousBackup.log", ios::out | ios::app)
  {
  }

ostream& now(ostream& os)
  {
  time_t tt = time(0);
  char buf[50];
  strftime(buf, sizeof(buf), "%y/%m/%d.%H:%M:%S: ", localtime(&tt));
  os << buf; 
  return os;
  }

globals.h

Synopsis
#pragma once

#include <ostream>
#include <fstream>
using std::ofstream;
using std::ostream;
using std::endl;

class Globals
  {
  public:
    Globals();
    ofstream Log;
  } ;

ostream& now(ostream& os);
extern Globals globals;

jContinuousBackup.cpp

Synopsis
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include "globals.h"
#include "DirInfo.h"
#include "Notification.h"
#include "ConfigFile.h"
#include "CompareDirTrees.h"

class App : public Notification::Functor, public CompareDirTrees::Functor
  {
  public:
    void Run()
      {
      SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);

      Notification notify(*this);
      ConfigFile cf(notify);
      cf.Load();

      //force a comparison first time through
      notify.ForceComparison();
      notify.DetectChanges();
      notify.Term();
      }

    void operator() (const DirInfo& di)
      {
      CompareDirTrees cdt;
      cdt.Compare(di.dir, di.backupdir, di.subdirs, *this);
      }

    void operator() (const string& srcdir, const string& destdir, const string& filename)
      {
      globals.Log << now 
        << "Changed: dir=" << srcdir 
        << " backupdir=" << destdir
        << " file=" << filename
        << endl;
      ::CopyFile(string(srcdir + "\\" + filename).c_str(), string(destdir + "\\" + filename).c_str(), 0);
      }
  } ;

int __stdcall WinMain(struct HINSTANCE__*, struct HINSTANCE__*, char*, int)
  {
  App app;
  app.Run();
  return 0;
  }

Notification.cpp

Synopsis
#include <string>
using namespace std;

#include "Notification.h"
#include "NotificationHandles.h"

class Notification::Private
  {
  public:
    Private(Functor& fn)
      : mCallback(fn)
      {
      }
    NotificationHandles mHandles;
    Functor& mCallback;
  } ;


Notification::Notification(Functor& fn)
: impl(* new Notification::Private(fn))
  {
  }
Notification::~Notification()
  {
  delete &impl;
  }
void Notification::Add(const string& dir, bool subdirs, const string& backupdir)
  {
  impl.mHandles.Add(dir, subdirs, backupdir);
  }
void Notification::ForceComparison()
  {
  for(impl.mHandles.Reset(); impl.mHandles.More(); )
    impl.mCallback(impl.mHandles.Current());
  }
void Notification::DetectChanges()
  {
  for(;;)
    {
    int index = impl.mHandles.WaitForChange();
    if (impl.mHandles.IsExit(index))
      return;

    impl.mCallback(impl.mHandles.InfoAt(index));
    impl.mHandles.Refresh();
    }
  }

void Notification::Term()
  {
  impl.mHandles.Term();
  }


Notification.h

Synopsis
#pragma once
#include <string>
using std::string;

#include "DirInfo.h"

class Notification
  {
  public:
    struct Functor 
      { 
      virtual void operator() (const DirInfo& di) = 0; 
      } ;

    Notification(Functor& fn);
    Notification::~Notification();

    void Add(const string& dir, bool subdirs, const string& backupdir);
    void ForceComparison();
    void DetectChanges();
    void Term();

  private:
    class Private;
    Private& impl;
  } ;


NotificationHandles.cpp

Synopsis
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <vector>
using namespace std;

#include "globals.h"
#include "NotificationHandles.h"

struct HandleInfo
  {
  HANDLE handle;
  DirInfo dirinfo;
  } ;

typedef vector<HandleInfo> Handles;

class NotificationHandles::Private
  {
  public: 
    Handles mHandles;
    int mCurrentIndex;
  } ;


NotificationHandles::NotificationHandles()
: impl(* new NotificationHandles::Private)
  {
  HandleInfo hi;
  hi.dirinfo.dir = "exit";
  hi.dirinfo.backupdir = "exit";
  hi.dirinfo.subdirs = false;

  hi.handle = CreateEvent(0, FALSE, FALSE, 0);
  if (hi.handle == INVALID_HANDLE_VALUE)
    {
    globals.Log << now << "Bad handle on CreateEvent gle=" << GetLastError() << endl;
    exit(0);
    }

  impl.mHandles.push_back(hi);
  }

NotificationHandles::~NotificationHandles()
  {
  Term();
  delete &impl;
  }


bool NotificationHandles::IsExit(int i)
  {
  return i == 0;
  }
DirInfo NotificationHandles::InfoAt(int i)
  {
  return impl.mHandles[i].dirinfo;
  }
void NotificationHandles::Reset()
  {
  impl.mCurrentIndex = -1;
  }
bool NotificationHandles::More()
  {
  for(impl.mCurrentIndex++; impl.mCurrentIndex < (int) impl.mHandles.size(); impl.mCurrentIndex++)
    {
    if (InfoAt(impl.mCurrentIndex).dir != "exit")
      break;
    }
  return impl.mCurrentIndex < (int) impl.mHandles.size();
  }
DirInfo NotificationHandles::Current()
  {
  return InfoAt(impl.mCurrentIndex);
  }
void NotificationHandles::SignalToExit()
  {
  //TBD
  }

void NotificationHandles::Add(const string& dir, bool subdirs, const string& backupdir)
  {
  HandleInfo hi;
  hi.dirinfo.dir = dir;
  hi.dirinfo.subdirs = subdirs;
  hi.dirinfo.backupdir = backupdir;

  hi.handle = FindFirstChangeNotification(
    dir.c_str(),  //path
    subdirs ? 1 : 0, //watch sub tree
    FILE_NOTIFY_CHANGE_FILE_NAME | 
    FILE_NOTIFY_CHANGE_DIR_NAME |
    FILE_NOTIFY_CHANGE_LAST_WRITE);

  if (hi.handle == INVALID_HANDLE_VALUE)
    {
    globals.Log << now << "Bad handle on findfirstchange gle=" << GetLastError() << endl;
    globals.Log << "dir=" << dir << endl;
    return;
    }
  impl.mHandles.push_back(hi);
  }

int NotificationHandles::WaitForChange()
  {
  HANDLE* handles = new HANDLE[impl.mHandles.size()];
  int i = 0;
  for(Handles::iterator it = impl.mHandles.begin(); it != impl.mHandles.end(); ++it)
    {
    handles[i] = (*it).handle;
    i++;
    }
  DWORD rc = WaitForMultipleObjects(impl.mHandles.size(), handles, FALSE, INFINITE); 
  if (rc == WAIT_TIMEOUT)
    throw "timeout occurred!";

  DWORD index = rc - WAIT_OBJECT_0;
  if (index < 0 || index >= impl.mHandles.size())
    {
    globals.Log << now << "error on WaitForMultiple index=" << index << " gle=" << GetLastError() << endl;
    throw "error occurred";
    }

  //os << "Change detected: handle=" << index << endl;

  return index;

  }
void NotificationHandles::Refresh()
  {
  int i = 0;
  for(Handles::iterator it = impl.mHandles.begin(); it != impl.mHandles.end(); ++it)
    {
    if (i != 0)
      {

      BOOL wrc = FindNextChangeNotification((*it).handle);
      if (!wrc)
        {
        globals.Log << now << "FindNextChangeNotification failed: gle=" << GetLastError() << endl;
        globals.Log << "   dir=" << (*it).dirinfo.dir << endl;
        }
      }
    i++;
    }
  }
void NotificationHandles::Term()
  {
  int i = 0;
  for(Handles::iterator it = impl.mHandles.begin(); it != impl.mHandles.end(); ++it)
    {
    if (i == 0)
      CloseHandle((*it).handle);
    else
      {
      BOOL wrc = FindCloseChangeNotification((*it).handle);
      if (!wrc)
        {
        globals.Log << now << "FindCloseChangeNotification failed: gle=" << GetLastError() << endl;
        globals.Log << "   dir=" << (*it).dirinfo.dir << endl;
        }
      }
    i++;
    }
  }

//FILE_NOTIFY_CHANGE_FILE_NAME
//FILE_NOTIFY_CHANGE_DIR_NAME
//FILE_NOTIFY_CHANGE_ATTRIBUTES
//FILE_NOTIFY_CHANGE_SIZE
//FILE_NOTIFY_CHANGE_LAST_WRITE
//if a first-level subdir is changed, the '.' file is changed and
// so the Last_write is also changed 
//but if a file is touched, this is the only one that triggers.
//FILE_NOTIFY_CHANGE_SECURITY 

NotificationHandles.h

Synopsis
#pragma once
#include "DirInfo.h"

#include <string>
using std::string;

class NotificationHandles
  {
  public:
    NotificationHandles();
    ~NotificationHandles();
    bool IsExit(int i);
    DirInfo InfoAt(int i);
    void SignalToExit();
    void Add(const string& dir, bool subdirs, const string& backupdir);
    int WaitForChange();
    void Refresh();
    void Term();
    void Reset();
    bool More();
    DirInfo Current();

  private:
    class Private;
    Private& impl;
  } ;







Contact me about content on this page using john_web-at-arrizza-dot-com
For Web Master or site problems contact: webadmin-at-arrizza-dot-com
Copyright John Arrizza (c) 2001-2010