
"""
Flash backup tool by Connelly Barnes, public domain.

Syntax:

  python backup.py rootsrcdir srcdir1 [srcdir2 ... srcdirn] backupdir
  
Backs up srcdir1 ... srcdirn to backupdir.  The rootsrcdir prefix must be a
prefix of all source directories, and rootsrcdir maps to backupdir.  Prints
a summary of copying operations which will take place during the backup and
asks for confirmation before any disc writes are made.  Successive uses of
backup.py will cause an existing backup in backupdir to be updated.

Example:

  python backup.py D:\ D:\Art D:\Documents D:\Code D:\Pictures G:\Backup

This copies D:\Art => G:\Backup\Art, D:\Documents => G:\Backup\Documents, etc.
"""

from autoimp import *

def is_hidden_file(filename):
  return win32api.GetFileAttributes(filename) & win32con.FILE_ATTRIBUTE_HIDDEN

def stat_stamp(st):
  return (st.st_size, st.st_mtime)

def dir_parents(dirname):
  dirname = os.path.abspath(dirname)
  if dirname.endswith('\\') or dirname.endswith('/'):
    dirname = dirname[:-1]
  while True:
    if '\\' in dirname or '/' in dirname:
      yield dirname
    else:
      yield dirname + '\\'
    if '\\' in dirname:
      dirname = dirname[:dirname.rindex('\\')]
    elif '/' in dirname:
      dirname = dirname[:dirname.rindex('/')]
    else:
      return

def prepare_backup(rootsrcdir, srcdirs, destdir, query=True, more_verbose=False):
  """
  Prepare for backup, return (srcfiles, destfiles): two lists of the same length for file copying.
  
  If query is True then be verbose and ask for confirmation and call sys.exit() if not confirmed.
  """
  if query:
    print 'Back up directories:'
  srcfiles = []
  destfiles = []

  total = 0
  total_delta = 0
  count = 0
  for srcdir in srcdirs:
    dirsize = 0
    dirdelta = 0
    for (dirpath, dirnames, filenames) in os.walk(srcdir):
      assert dirpath[:len(srcdir)] == srcdir
      assert srcdir.startswith(rootsrcdir)
      relpath = dirpath[len(rootsrcdir):]
      if relpath.startswith('\\') or relpath.startswith('/'):
        relpath = relpath[1:]
      dest = os.path.join(destdir, relpath)
      if any(is_hidden_file(x) for x in dir_parents(dirpath)):
        continue
      for filename in filenames:
        fullname = os.path.join(dirpath, filename)
        if not is_hidden_file(fullname):
          src_stat = os.stat(fullname)
          destname = os.path.join(dest, filename)
          delta = src_stat.st_size
          if os.path.exists(destname):
            dest_stat = os.stat(destname)
            delta -= dest_stat.st_size
            if stat_stamp(src_stat) == stat_stamp(dest_stat):
              continue
          total += src_stat.st_size
          dirsize += src_stat.st_size
          dirdelta += delta
          total_delta += delta
          count += 1
          srcfiles.append(fullname)
          destfiles.append(destname)
          if more_verbose:
            print 'Will copy', fullname, '=>', destname, ('(copy %.1f MB)' % (src_stat.st_size/1024**2.0))
    if query:
      print srcdir, '=>', os.path.join(destdir, srcdir[len(rootsrcdir):]), '(copy %.3f GB, change %.3f GB)' % (dirsize/1024**3.0, dirdelta/1024**3.0)
        
  if query:
    print
    print 'Total copy size:      %.3f GB' % (total / 1024**3.0)
    print 'Total change in size: %.3f GB' % (total_delta / 1024**3.0)
    print 'Number of files:     ', count
    print
    
    s = raw_input('Continue (yes/no)?')
    if not s.lower().startswith('y'):
      sys.exit(1)

  return (srcfiles, destfiles)


def do_backup(srcfiles, destfiles, verbose=True):
  """
  Perform backup, copying srcfiles[i] => destfiles[i] for all i.
  """
  print 'Copying:'
  assert len(srcfiles) == len(destfiles)
  for i in range(len(srcfiles)):
    destdir = os.path.split(destfiles[i])[0]
    if not os.path.exists(destdir):
      os.makedirs(destdir)
    if verbose:
      print srcfiles[i], '=>', destfiles[i]
    shutil.copyfile(srcfiles[i], destfiles[i])
    shutil.copystat(srcfiles[i], destfiles[i])
  print 'Done'
  

def main():
  args = sys.argv[1:]
  if len(args) < 3:
    print __doc__.strip()
    return
  rootsrcdir = args[0]
  srcdirs = args[1:-1]
  destdir = args[-1]

  (srcfiles, destfiles) = prepare_backup(rootsrcdir, srcdirs, destdir)
  do_backup(srcfiles, destfiles)


if __name__ == '__main__':
  main()
