import libdar
import os, sys

# mandatory first action is to initialize libdar
# by calling libdar.get_version()

u = libdar.get_version()
print("using libdar version {}.{}.{}".format(u[0],u[1],u[2]))

# libdar.get_version() can be called at will

# now defining a very minimalist class to let libdar
# interact with the user directly. One could make use
# of graphical popup window here if a Graphical User Interface
# was used:
class myui(libdar.user_interaction):
    def __init__(self):
        libdar.user_interaction.__init__(self)
        # it is mandatory to initialize the parent
        # class: libdar.user_interaction

    def inherited_message(self, msg):
        print("LIBDAR MESSAGE:{0}".format(msg))
        # the "LIBDAR MESSAGE" is pure demonstration
        # to see when output comes through this
        # user_interaction child class

    def inherited_pause(self, msg):
        while True:
            res = input("LIBDAR QUESTION:{0} y/n ".format(msg))
            if res == "y":
                return True
            else:
                if res == "n":
                    return False
                else:
                    print("answer 'y' or 'n'")

    def inherited_get_string(self, msg, echo):
        return input(msg)
        # we should take care about the boolean value echo
        # and not show what user type when echo is False

    def inherited_get_secu_string(self, msg, echo):
        return input(msg)
        # we should take care about the boolean value echo
        # and not show what user type when echo is False

# exceptions from libdar (libdar::Egeneric, Erange, ...) in
# C++ side are all translated to libdar.darexc class introduced
# in the python binding. This class has the __str__() method to
# get the message string about the cause of the exception
# it get displayed naturally when you don't catch them from
# a python shell

# here is an example on how to handle libdar.darexc exceptions:

try:
    x = libdar.deci("not an integer")
except libdar.darexc as obj:
    print("libdar exception: {}".format(obj.__str__()))

# here follows some helper routines as illustration
# on how to manage some libdar data structures

# display libdar.statistics (not all fields are shown, see help(libdar.statistics)
def display_stats(stats):
    print("---- stats result ---")
    print("treated entries  = {}".format(stats.get_treated_str()))
    print("hard link entries = {}".format(stats.get_hard_links_str()))
    print("%skipped entries = {}".format(stats.get_skipped_str()))
    print("inode only entries  = {}".format(stats.get_inode_only_str()))
    print("ignored entries  = {}".format(stats.get_ignored_str()))
    print("too old entries  = {}".format(stats.get_tooold_str()))
    print("errored entries  = {}".format(stats.get_errored_str()))
    print("deleted entries  = {}".format(stats.get_deleted_str()))
    print("EA      entries  = {}".format(stats.get_ea_treated_str()))
    print("FSA     entries  = {}".format(stats.get_fsa_treated_str()))
    print("hard link entries = {}".format(stats.get_hard_links_str()))
    print("wasted byte amount = {}".format(stats.get_byte_amount_str()))
    print("total enries = {}".format(i2str(stats.total())))
    print("---------------------")

# displaying libdar.entree_stats there is a predefined method
# as used here that will rely on a libdar.user_interaction to
# display the contents. You may also access the different fields
# by hand. see help(libdar.entree_stats) for details
def display_entree_stats(stats, ui):
    print("--- archive content stats ---")
    stats.listing(ui)
    print("-----------------------------")

# for this later structure (libdar.entree_stats) you will probably
# want to play with libdar.infinit. I has quite all operation you
# can expect on integer (*,/,+,-,*=,+=,-=,/=,^=, >>=, <<=, %=,<,>,==,!=,...)
# the libdar.deci() class can be buil from a libdar.infinit or
# from a python string and provides two methods: human() and computer()
# that return a python string representing the number in base ten
# and computer() that returns a libdar.infinint

def f0():
    x = libdar.infinint("122")
    #
    dy = libdar.deci("28")
    y = dy.computer()
    # which is equivalent to y = libdar.infinint("28")

    z = x / y # integer division
    print("the integer division of {} by {} gives {}".format(libdar.deci(x).human(),
                                                             dy.human(),
                                                             libdar.deci(z).human()))

    # there is also the libdar.euclide(x, y) method that returns
    # the integer division and rest as a couple of their numerator and divisor
    # passed in argument:

    res = libdar.euclide(x, y)
    print("{} / {} = {} with a remaining of {}".format(libdar.deci(x).human(),
                                                    dy.human(),
                                                    libdar.deci(res[0]).human(),
                                                    libdar.deci(res[1]).human()))

# libdar.infinint to string:
def i2str(infinint):
    return libdar.deci(infinint).human()

# this is a example of routine that given an open libdar.archive
# will provide its listing content. This call is recursive but
# free to you to recurse or not upon user event (expanding a directory
# in the current display for example).
# Note that the method libdar.archive.get_children_in_table returns
# a list of object of type libdar.list_entry which has a long
# list of methods to provide a very much detailed information
# for a given entry in the archive. For more about it,
# see help(libdar.list_entry)
def list_dir(archive, chem = "", indent = ""):
    content = archive.get_children_in_table(chem, True)
    # contents is a list of libdar.list_entry objects

    for ent in content:
        ligne = indent
        if ent.is_eod():
            continue

        if ent.is_hard_linked():
            ligne += "*"
        else:
            ligne += " "

        if ent.is_dir():
            ligne += "d"
        else:
            if ent.is_file():
                ligne += "f"
            else:
                 if ent.is_symlink():
                     ligne += "l"
                 else:
                     if ent.is_char_device():
                         ligne += "c"
                     else:
                         if ent.is_block_device():
                             ligne += "b"
                         else:
                             if ent.is_unix_socket():
                                 ligne += "s"
                             else:
                                 if ent.is_named_pipe():
                                     ligne += "p"
                                 else:
                                     if ent.is_door_inode():
                                         ligne += "D"
                                     else:
                                         if ent.is_removed_entry():
                                             ligne += "Removed entry which was of of type {}".format(ent.get_removed_type())
                                         else:
                                             ligne += "WHAT THIS????"
                                             continue

        ligne += ent.get_perm() + " " + ent.get_name() + " "
        ligne += ent.get_uid(True) + "/" + ent.get_gid(True) + " "
        ligne += ent.get_last_modif()
        print(ligne)

        # now peparing the recursion when we meet a directory:
        if ent.is_dir():
            if chem != "":
                nchem = (libdar.path(chem) + ent.get_name()).display()
            else:
                nchem = ent.get_name()
            nindent = indent + "   "
            list_dir(archive, nchem, nindent)

# in the following we will provide several functions that
# either create, read, test, diff or extract an archive
# all will rely on the following global variables:

ui = myui()
sauv_path = libdar.path(".")
arch_name = "arch1"
ext = "dar"

# let's create an archive. the class
# libdar.archive_options_create has a default constructor
# that set the options to their default values, the clear()
# method can also be used to reset thm to default.
# then a bunch of method are provided to modify each of them
# according to your needs. See help(libdar.archive_options_create)
# and the API reference documentation for their nature and meaning

# the libdar.path can be set from a python string but has some
# method to pop, add a sub-directory easily. the libdar.path.display()
# method provides the representative string of the path
def f1():
    opt = libdar.archive_options_create()
    opt.set_info_details(True)
    opt.set_display_treated(True, False)
    opt.set_display_finished(True)
    fsroot = libdar.path("/etc")
    print("creating the archive")
    libdar.archive(ui,
                   fsroot,
                   sauv_path,
                   arch_name,
                   ext,
                   opt)

# a the difference of C++ here several constructors and method
# like op_diff, op_test, etc. where a libdar::statistics *progresive_report
# field is present, the python binding has two equivalent methods, one
# without this field, and a second with a plain libdar.statistics field.
# this later object can be read from another thread while a libdar operation runs
# with it given as argument. This let the user see the progression of the
# operation (mainly counters on the number of inode
# treated, skipped, errored, etc.). More detail in the API reference guide

# by the way you will see user interaction in action as we
# tend to overwrite the archive created in f1(), assuming you
# run f1(), f2().... in order for the demo

def f2():
    opt = libdar.archive_options_create()
    opt.set_info_details(True)
    opt.set_display_treated(False, False)
    opt.set_display_finished(True)
    fsroot = libdar.path("/etc")
    stats = libdar.statistics()

    print("creating the archive")
    libdar.archive(ui,
                   fsroot,
                   sauv_path,
                   arch_name,
                   ext,
                   opt,
                   stats)
    display_stats(stats)


# here we read an existing archive. Then first
# phase is to create a libdar.archive object
# the second is to act upon it. Several actions
# can be done in sequence on an existing object
# open that way (extracting several time, diff, test,
# an do on
def f3():
    opt = libdar.archive_options_read()
    opt.set_info_details(True)
    arch1 = libdar.archive(ui,
                           sauv_path,
                           arch_name,
                           ext,
                           opt);
    list_dir(arch1)
    stats = arch1.get_stats()
    display_entree_stats(stats, ui)

# below we will play with mask. Most operation have to
# operations to filter the file they will apply on. Then
# first "set_selection()" applies to filenames only
# the second 'set_subtree()" applies the whole path instead
# What type of libdar.mask() you setup for these is
# completely free. Pay attention that when a directory
# is excluded (by mean of set_subtree()) all its content
# and recursively all is subdirectories are skipped.

# the list of class inheriting from libdar.mask() are:
# - bool_mask(bool) either always true or always false
# - libdar.simple_mask(string) the provided string is read as a glob
# expression, which is the syntax most shell use like bash
# - libdar.regex_mask(string) the argument is read as a
# regular expression
# - simple_path_mask(string) matches if the string to
# compare to is a subdir of the string provided to the
# constructor, or if this string is a subdr of the string
# to compare to. This is mostly adapted to select a
# given directory for an operation, as all the path leading
# to it must match and all subdirectory in that directory
# must also match.
# - same_path_mask(string) matches only the given
# argument. This is intended for directory pruning
#
# most mask have in fact a second argument in their
# constructor (a boolean) that define whether the mask
# is case sensitive (True) or not (False)

# - not_mask(mask) gives the negation of the mask
# provided in argument
# - et_mask() + add_mask(mask) + add_mask(mask) +...
# makes a logical AND between the added masks
# - ou_mask() + add_mask(mask) + add_mask(mask) +...
# makes a logical OR between the added masks
# why this French "et" and "ou" words? because at that
# time they were added this code was internal to dar
# and I frequently use French words to designate my
# own datastructure to differentiate with English symbols
# brought from outside. This code has not change since then
# so is the reason.
# of course you can add_mask() a ou_mask(), a not_mask()
# or yet a et_mask() recursively at will and make arbitrarily complex
# masks mixing them with simple_mask(), regular_mask(), and so on.

def f4():
    opt = libdar.archive_options_read()
    arch1 = libdar.archive(ui,
                           sauv_path,
                           arch_name,
                           ext,
                           opt);

    opt = libdar.archive_options_test()

    # defining which file to test bases on filename (set_selection)
    mask_file1 = libdar.simple_mask("*.*", True)
    mask_file2 = libdar.regular_mask(".*\.pub$", True)
    mask_filenames = libdar.ou_mask() # doing the logical OR between what we will add to it:
    mask_filenames.add_mask(mask_file1)
    mask_filenames.add_mask(mask_file2)
    opt.set_selection(mask_filenames)

    # reducing the testing in subdirectories
    tree1 = libdar.simple_path_mask("/etc/ssh", False)
    tree2 = libdar.simple_path_mask("/etc/grub.d", False)
    tree = libdar.et_mask() # doing the loical AND betwen what we will add to it:
    tree.add_mask(libdar.not_mask(tree1))
    tree.add_mask(libdar.not_mask(tree2))
    opt.set_subtree(tree)

    opt.set_info_details(True)
    opt.set_display_skipped(True)

    arch1.op_test(opt)

# nothing much more different as previously
# except that we compare the archive with
# the filesystem (op_diff) while we tested the
# archive coherence previously (op_test)
def f5():
    opt = libdar.archive_options_read()
    arch1 = libdar.archive(ui,
                           sauv_path,
                           arch_name,
                           ext,
                           opt);

    tree1 = libdar.simple_path_mask("/etc/ssh", False)
    tree2 = libdar.simple_path_mask("/etc/grub.d", False)
    tree = libdar.ou_mask()
    tree.add_mask(tree1)
    tree.add_mask(tree2)

    opt = libdar.archive_options_diff()
    opt.set_subtree(tree)
    opt.set_info_details(True)
    opt.set_display_treated(True, False)
    opt.set_ea_mask(libdar.bool_mask(True))
    opt.set_furtive_read_mode(False)

    arch1.op_diff(libdar.path("/etc"),
                  opt)

    rest = libdar.path("./Restore")
    try:
        os.rmdir(rest.display())
    except:
        pass
    os.mkdir(rest.display())

    opt = libdar.archive_options_extract()

    # the overwriting policy can receive
    # objects from many different crit_action_* classes
    # - crit_constant_action() used here does always the same
    # action on Data and EA+FSA when a conflict arise that
    # would lead to overwriting
    # - testing(criterium) the action depends on the evaluation
    # of the provided criterium (see below)
    # - crit_chain() + add(crit_action) performs the different
    # crit_actions added in sequence the first one that provides
    # an action for Data and/or EA+FSA is retained. If no action
    # is left undefined the following crit_action of the chain are
    # not evaluated
    #
    # for the testing crit_action inherited class, we need to provide
    # a criterium object. Here too there is a set of inherited classes
    # that come to help:
    # - crit_in_place_is_inode
    # - crit_in_place_is_dir
    # - crit_in_place_is_file
    # - ...
    # - crit_not (to take the negation of the given criterium)
    # - crit_or + add_crit() + add_crit() ... makes the logical OR
    # - crit_and + add_crit() + add_crit()... for the logical AND
    # - crit_invert for the in_place/to_be_added inversion
    # Read the manual page about overwriting policy for details
    # but in substance the criterum return true of false for each
    # file in conflict and the object if class testing that uses
    # this criterium applies the action given as "go_true" or the
    # action given as "go_false" in regard of the provided result

    over_policy = libdar.crit_constant_action(libdar.over_action_data.data_preserve,
                                           libdar.over_action_ea.EA_preserve)
    opt.set_overwriting_rules(over_policy)

    # fsa_scope is a std::set in C++ side and translates to a
    # python set on python side. Use the add() method to add
    # values to the set:
    fsa_scope = set()
    fsa_scope.add(libdar.fsa_family.fsaf_hfs_plus)
    fsa_scope.add(libdar.fsa_family.fsaf_linux_extX)
    opt.set_fsa_scope(fsa_scope)

    stats = libdar.statistics()
    arch1.op_extract(rest, opt, stats)
    display_stats(stats)


# last, all operation that interact with filesystem use by default
# a libdar.entrepot_local object (provided by the archive_options_*
# object, this makes the archive written and read from local filesystem.
# However you can replace this entrepot by an object of class
# libdar.libcurl_entrepot to read or write an archive over the network
# directly from libdar by mean of FTP of SFTP protocols. Follows an
# illustration of this possibility:
def f6():
    opt = libdar.archive_options_read()
    passwd ="joe@the.shmoe"
    secu_pass = libdar.secu_string(passwd, len(passwd))
    entrepot = libdar.entrepot_libcurl(ui,
                                       libdar.mycurl_protocol.proto_ftp,
                                       "anonymous",
                                       secu_pass,
                                       "ftp.edrusb.org",
                                       "",
                                       False,
                                       "",
                                       "",
                                       "",
                                       5)
    print(entrepot.get_url())
    opt.set_entrepot(entrepot)
    opt.set_info_details(True)

    arch2 = libdar.archive(ui,
                           libdar.path("/dar.linux.free.fr/Python_tutorial"),
                           "example",
                           "dar",
                           opt)

    opt2 = libdar.archive_options_test()
    opt2.set_display_treated(True, False)
    arch2.op_test(opt2)

# other classes of interest:
# - libdar.database for the dar_manager featues
# - libdar.libdar_xform for the dar_xform features
# - libdar.libdar_slave for the dar_slave features

# they are all three accessible from python and follow
# very closely the C++ syntax and usage
# thanks to refer to the API documentation or to the
# C++ tutorial
