myarchive.py

#!/usr/bin/env python3.9
# -*- coding: iso-8859-1 -*-
# FSArchiver is a system tool that allows you to save the contents of a
# file system to a compressed archive file. The file system can be restored
# on a partition which has a different size and it can be restored on a
# different file system.
# Unlike tar/dar, FSArchiver also creates the file system when it extracts
# the data to partitions.
# Everything is checksummed in the archive in order to protect the data. If
# the archive is corrupt, you just lose the current file, not the whole archive.
#
# It is possible to make consistent backups met fsarchiver if you are using LVM !
# see http://www.system-rescue-cd.org/lvm-guide-en/Making-consistent-backups-with-LVM/
#
# To start this GUI application , you need root rights !
#
"""
A GUI for fsarchiver using Python3 and Gtk3.0
Author:  Franz Ulenaers
Version: 1.0
Date: 2017/10/12
"""

import gi, os, time, threading
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk

global printdone, intervals

printdone = 1
intervals = (
    ('uren', 3600),
    ('minuten', 60),
    ('seconden', 1),
    )

def display_time(seconds, granularity=3):
    result = []
    for name, count in intervals:
        value = seconds // count
        if value:
            seconds -= value * count
            if value == 1:
                name = name.rstrip('s')
            result.append("{} {}".format(value, name))
    return ', '.join(result[:granularity])


class ArchiverGUI(object):
    def __init__(self):
        window = Gtk.Window()
        window.connect("destroy", Gtk.main_quit)
        vbox = Gtk.VBox(homogeneous=False, spacing=3, expand=False)

        g = Gtk.Grid()
        g.set_border_width(10)
        g.set_row_spacing(5)
        g.set_column_spacing(5)
        vbox.add(g)

        offset = 0
        frame = self.new_frame(g, offset, 7)
        label = Gtk.Label()
        label.set_markup('<span font="12" weight="bold">Backup een bestandssysteem met fsarchiver</span>')
        g.attach(label, 0, 0, 2, 1)
        labels = ["<b> Bestandsystemen te archiveren:</b>",
            "<b> Foldernaam voor gearchiveerd bestand:</b>",
            "<b> Gearchiveerd bestandsnaam:</b>",
            "<b> Compressie nummer:</b>",
            "<b> Aantal cpus/threads:</b>",
            "<b> Splitsen in aantal blokken van 1G</b>"]
        for i in range(6):
            label = Gtk.Label()
            label.set_markup(labels[i])
            g.attach(label, 0, 1 + i, 1, 1)

        self.dev_list = self.get_device_labels()

        self.fs_to_archive = Gtk.ComboBoxText.new_with_entry()
        for d in self.dev_list:
            self.fs_to_archive.append_text(d)
        self.fs_to_archive.connect("changed", self.set_default_archive_filename)
        g.attach(self.fs_to_archive, 1, 1, 1, 1)

        self.archive_file_path = Gtk.FileChooserButton()
        self.archive_file_path.set_title("Directory in which to save archive")
        self.archive_file_path.set_current_folder("/")
        self.archive_file_path.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
        g.attach(self.archive_file_path, 1, 2, 1, 1)

        self.create_archive_filename = Gtk.Entry()
        g.attach(self.create_archive_filename, 1, 3, 1, 1)

        self.compression_level = Gtk.ComboBoxText()
        for i in range(1, 23):
            self.compression_level.append_text(str(i))
        g.attach(self.compression_level, 1, 4, 1, 1)

        self.number_of_threads = Gtk.ComboBoxText()
        for i in range(1, 6):
            self.number_of_threads.append_text(str(i))
        g.attach(self.number_of_threads, 1, 5, 1, 1)

        self.number_of_blocks = Gtk.ComboBoxText()
        for i in range(1, 7):
            self.number_of_blocks.append_text(str(i))
        g.attach(self.number_of_blocks, 1, 6, 1, 1)
        #============================

        offset = 8
        frame = self.new_frame(g, offset, 5)

        label = Gtk.Label()
        label.set_markup('<span font="12" weight="bold">Terugzetten gearchiveerd bestandssysteem</span>')

        g.attach(label, 0, offset, 2, 1)

        labels = ["<b>Archiveer bestandsnaam:</b>",
            "<b>Positie in gearchiveerd bestand:</b>",
            "<b>Terugzetten van bestandssysteem:</b>"]
        for i in range(3):
            label = Gtk.Label()
            label.set_markup(labels[i])
            g.attach(label, 0, offset + 1 + i, 1, 1)

        self.restore_archive_filename = Gtk.FileChooserButton()
        self.restore_archive_filename.set_title("Bestand bevat gearchiveerd bestandssysteem:")
        self.restore_archive_filename.set_current_folder("/")
        self.restore_archive_filename.connect("file-set", self.show_info_window)
        g.attach(self.restore_archive_filename, 1, offset + 1, 1, 1)

        self.archive_position = Gtk.ComboBoxText.new_with_entry()
        self.archive_position.append_text("0")
        g.attach(self.archive_position, 1, offset + 2, 1, 1)

        self.destination_filesystem = Gtk.ComboBoxText.new_with_entry()
        for d in self.dev_list:
            self.destination_filesystem.append_text(d)
        g.attach(self.destination_filesystem, 1, offset + 3, 1, 1)

        #============================

        offset = 13
        frame = self.new_frame(g, offset, 3)

        btn_box = Gtk.HButtonBox()
        frame.add(btn_box)

        btn_box.set_layout(Gtk.ButtonBoxStyle.SPREAD)
        btn = Gtk.Button.new_from_stock(Gtk.STOCK_QUIT)
        btn.connect("clicked", Gtk.main_quit)
        btn_box.add(btn)
        btn = Gtk.Button.new_from_stock(Gtk.STOCK_EXECUTE)
        btn.connect("clicked", self.gather_the_info)
        btn_box.add(btn)

        #============================

        offset = 16
        #frame = self.new_frame(g,offset,3)

        self.status_label = Gtk.Label(label="Hier komt de status")
        #frame.add(self.status_label)
        g.attach(self.status_label, 0, 16, 4, 1)

        #============================

        window.add(vbox)
        window.show_all()

        self.info_window = Gtk.Dialog("Gearchiveerde Informatie")
        c = self.info_window.get_content_area()
        self.archive_info = Gtk.TextView()
        c.add(self.archive_info)
        a = self.info_window.get_action_area()
        btn = Gtk.Button.new_from_stock(Gtk.STOCK_CLOSE)
        btn.connect("clicked", lambda x: self.info_window.hide())
        a.add(btn)

    # ---------------------------------

    def display_time(seconds, granularity=3):
        result = []
        for name, count in intervals:
            value = seconds // count
            if value:
                seconds -= value * count
                if value == 1:
                    name = name.rstrip('s')
                result.append("{} {}".format(value, name))
        return ', '.join(result[:granularity])

    # ----------------------------------

    def new_frame(self, grid, top, height):
        frame = Gtk.Frame()
        # frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
        grid.attach(frame, 0, top, 3, height)
        return frame

    # ---------------------------------

    def gather_the_info(self, widget):
        global printdone
        global display_time
        starttime = time.strftime('%H:%M:%S', time.localtime())

        target = self.fs_to_archive.get_active_text()
        target = target.strip()
        try:
            archive_name = self.restore_archive_filename.get_filename()
        except:
            archive_name = ""
        if target:
            path = self.archive_file_path.get_filename()
            filename = self.create_archive_filename.get_text() + "-" + time.strftime("%Y%m%d%H%M") + ".fsa"
            compression_level = self.compression_level.get_active() + 1
            thread_count = self.number_of_threads.get_active() + 1
            block_count = (self.number_of_blocks.get_active() + 1)*1000
            my_cmd = "sudo fsarchiver -A -Z " + str(compression_level) + \
                " -s " + str(block_count) + \
                " -j " + str(thread_count) + " savefs " + path + \
                "/" + filename + " " + target
            #print ("my_cmd = ",my_cmd)
        elif archive_name:
            position = self.archive_position.get_active_text()
            destination = self.destination_filesystem.get_active_text()
            my_cmd = "sudo fsarchiver restfs " + archive_name + \
                " id=" + position + ",dest=" + destination
        print ("my_cmd = ",my_cmd)
        self.my_cmd = my_cmd
        self.archiver_busy_flag = 1
        t = threading.Thread(target=self.do_my_cmd)
        t.start()
        start_time = time.strftime('%H:%M:%S')
        num_sec = 0
        while self.archiver_busy_flag:
            while Gtk.events_pending():
                Gtk.main_iteration_do(False)
            self.status_label.set_text("Start: " + start_time + " Run time: " + display_time(num_sec, 3) + " running")
            time.sleep(1)
            num_sec += 1

        if self.archiver_busy_flag == 0:
            self.status_label.set_text("Start: " + start_time + " Run time: " + display_time(num_sec, 3) + " Done !")
            time.sleep(1)
            while printdone == 0:
                print ("fsarchiver commando : " + self.status_label.get_text())
                printdone = 1
        #

    # ---------------------------------

    def do_my_cmd(self):
        global printdone
        os.system(self.my_cmd)
        self.archiver_busy_flag = 0
        printdone = 0

    # ---------------------------------

    def show_info_window(self, widget):
        target = self.restore_archive_filename.get_filename()
        print ("Gearchiveerde informatie van bestand = ", target)
        my_cmd = 'sudo fsarchiver archinfo ' + target + ' 2>&1'
        archinfo_msg = ''
        print ("archinfo cmd = ", my_cmd)
        archinfo = os.popen(my_cmd, 'r')
        line = archinfo.readline()
        print (line)
        knt = 1
        while line:
            archinfo_msg = archinfo_msg + line
            line = archinfo.readline()
            print (line)
            knt += 1
            if knt == 3:
                words = line.split(' ')
                try:
                    archive_filecount = int(words[2])
                    for i in range(archive_filecount):
                        self.archive_position.append_text(str(i + 100))
                except:
                    continue
        b = self.archive_info.get_buffer()
        b.set_text(archinfo_msg)
        self.info_window.show_all()

    # ---------------------------------

    def get_device_labels(self):
        blkid = os.popen('sudo blkid -c /dev/null', 'r')
        dev_list = {}
        line = blkid.readline()
        while (line):
            mine = {}
            line = line.replace('/dev', 'DEVICE=/dev')
            line = line.replace(':', ' ')
            line = line.split(' ')
            for l in line:
                if l.strip():
                    m = l.split('=')
                    m[1] = m[1].replace('"', ' ')
                    mine[m[0]] = m[1].strip()
                break
            try:
                x = mine['LABEL']
            except:
                x = 'none'
            dev_list[mine['DEVICE']] = x
            line = blkid.readline()
        return dev_list

    # ---------------------------------

    def set_default_archive_filename(self, widget):
        f = self.fs_to_archive.get_active_text()
        self.create_archive_filename.set_text(self.dev_list[f])

    # ===============================


if __name__ == "__main__":
    app = ArchiverGUI()
    Gtk.main()
#!/usr/bin/env python3.9
# -*- coding: iso-8859-1 -*-
# FSArchiver is a system tool that allows you to save the contents of a
# file system to a compressed archive file. The file system can be restored
# on a partition which has a different size and it can be restored on a
# different file system.
# Unlike tar/dar, FSArchiver also creates the file system when it extracts
# the data to partitions.
# Everything is checksummed in the archive in order to protect the data. If
# the archive is corrupt, you just lose the current file, not the whole archive.
#
# It is possible to make consistent backups met fsarchiver if you are using LVM !
# see http://www.system-rescue-cd.org/lvm-guide-en/Making-consistent-backups-with-LVM/
#
# To start this GUI application , you need root rights !
#
"""
A GUI for fsarchiver using Python3 and Gtk3.0
Author:  Franz Ulenaers
Version: 1.0
Date: 2017/10/12
"""

import gi, os, time, threading
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk

global printdone, intervals

printdone = 1
intervals = (
    ('uren', 3600),
    ('minuten', 60),
    ('seconden', 1),
    )

def display_time(seconds, granularity=3):
    result = []
    for name, count in intervals:
        value = seconds // count
        if value:
            seconds -= value * count
            if value == 1:
                name = name.rstrip('s')
            result.append("{} {}".format(value, name))
    return ', '.join(result[:granularity])


class ArchiverGUI(object):
    def __init__(self):
        window = Gtk.Window()
        window.connect("destroy", Gtk.main_quit)
        vbox = Gtk.VBox(homogeneous=False, spacing=3, expand=False)

        g = Gtk.Grid()
        g.set_border_width(10)
        g.set_row_spacing(5)
        g.set_column_spacing(5)
        vbox.add(g)

        offset = 0
        frame = self.new_frame(g, offset, 7)
        label = Gtk.Label()
        label.set_markup('<span font="12" weight="bold">Backup een bestandssysteem met fsarchiver</span>')
        g.attach(label, 0, 0, 2, 1)
        labels = ["<b> Bestandsystemen te archiveren:</b>",
            "<b> Foldernaam voor gearchiveerd bestand:</b>",
            "<b> Gearchiveerd bestandsnaam:</b>",
            "<b> Compressie nummer:</b>",
            "<b> Aantal cpus/threads:</b>",
            "<b> Splitsen in aantal blokken van 1G</b>"]
        for i in range(6):
            label = Gtk.Label()
            label.set_markup(labels[i])
            g.attach(label, 0, 1 + i, 1, 1)

        self.dev_list = self.get_device_labels()

        self.fs_to_archive = Gtk.ComboBoxText.new_with_entry()
        for d in self.dev_list:
            self.fs_to_archive.append_text(d)
        self.fs_to_archive.connect("changed", self.set_default_archive_filename)
        g.attach(self.fs_to_archive, 1, 1, 1, 1)

        self.archive_file_path = Gtk.FileChooserButton()
        self.archive_file_path.set_title("Directory in which to save archive")
        self.archive_file_path.set_current_folder("/")
        self.archive_file_path.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
        g.attach(self.archive_file_path, 1, 2, 1, 1)

        self.create_archive_filename = Gtk.Entry()
        g.attach(self.create_archive_filename, 1, 3, 1, 1)

        self.compression_level = Gtk.ComboBoxText()
        for i in range(1, 23):
            self.compression_level.append_text(str(i))
        g.attach(self.compression_level, 1, 4, 1, 1)

        self.number_of_threads = Gtk.ComboBoxText()
        for i in range(1, 6):
            self.number_of_threads.append_text(str(i))
        g.attach(self.number_of_threads, 1, 5, 1, 1)

        self.number_of_blocks = Gtk.ComboBoxText()
        for i in range(1, 7):
            self.number_of_blocks.append_text(str(i))
        g.attach(self.number_of_blocks, 1, 6, 1, 1)
        #============================

        offset = 8
        frame = self.new_frame(g, offset, 5)

        label = Gtk.Label()
        label.set_markup('<span font="12" weight="bold">Terugzetten gearchiveerd bestandssysteem</span>')

        g.attach(label, 0, offset, 2, 1)

        labels = ["<b>Archiveer bestandsnaam:</b>",
            "<b>Positie in gearchiveerd bestand:</b>",
            "<b>Terugzetten van bestandssysteem:</b>"]
        for i in range(3):
            label = Gtk.Label()
            label.set_markup(labels[i])
            g.attach(label, 0, offset + 1 + i, 1, 1)

        self.restore_archive_filename = Gtk.FileChooserButton()
        self.restore_archive_filename.set_title("Bestand bevat gearchiveerd bestandssysteem:")
        self.restore_archive_filename.set_current_folder("/")
        self.restore_archive_filename.connect("file-set", self.show_info_window)
        g.attach(self.restore_archive_filename, 1, offset + 1, 1, 1)

        self.archive_position = Gtk.ComboBoxText.new_with_entry()
        self.archive_position.append_text("0")
        g.attach(self.archive_position, 1, offset + 2, 1, 1)

        self.destination_filesystem = Gtk.ComboBoxText.new_with_entry()
        for d in self.dev_list:
            self.destination_filesystem.append_text(d)
        g.attach(self.destination_filesystem, 1, offset + 3, 1, 1)

        #============================

        offset = 13
        frame = self.new_frame(g, offset, 3)

        btn_box = Gtk.HButtonBox()
        frame.add(btn_box)

        btn_box.set_layout(Gtk.ButtonBoxStyle.SPREAD)
        btn = Gtk.Button.new_from_stock(Gtk.STOCK_QUIT)
        btn.connect("clicked", Gtk.main_quit)
        btn_box.add(btn)
        btn = Gtk.Button.new_from_stock(Gtk.STOCK_EXECUTE)
        btn.connect("clicked", self.gather_the_info)
        btn_box.add(btn)

        #============================

        offset = 16
        #frame = self.new_frame(g,offset,3)

        self.status_label = Gtk.Label(label="Hier komt de status")
        #frame.add(self.status_label)
        g.attach(self.status_label, 0, 16, 4, 1)

        #============================

        window.add(vbox)
        window.show_all()

        self.info_window = Gtk.Dialog("Gearchiveerde Informatie")
        c = self.info_window.get_content_area()
        self.archive_info = Gtk.TextView()
        c.add(self.archive_info)
        a = self.info_window.get_action_area()
        btn = Gtk.Button.new_from_stock(Gtk.STOCK_CLOSE)
        btn.connect("clicked", lambda x: self.info_window.hide())
        a.add(btn)

    # ---------------------------------

    def display_time(seconds, granularity=3):
        result = []
        for name, count in intervals:
            value = seconds // count
            if value:
                seconds -= value * count
                if value == 1:
                    name = name.rstrip('s')
                result.append("{} {}".format(value, name))
        return ', '.join(result[:granularity])

    # ----------------------------------

    def new_frame(self, grid, top, height):
        frame = Gtk.Frame()
        # frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
        grid.attach(frame, 0, top, 3, height)
        return frame

    # ---------------------------------

    def gather_the_info(self, widget):
        global printdone
        global display_time
        starttime = time.strftime('%H:%M:%S', time.localtime())

        target = self.fs_to_archive.get_active_text()
        target = target.strip()
        try:
            archive_name = self.restore_archive_filename.get_filename()
        except:
            archive_name = ""
        if target:
            path = self.archive_file_path.get_filename()
            filename = self.create_archive_filename.get_text() + "-" + time.strftime("%Y%m%d%H%M") + ".fsa"
            compression_level = self.compression_level.get_active() + 1
            thread_count = self.number_of_threads.get_active() + 1
            block_count = (self.number_of_blocks.get_active() + 1)*1000
            my_cmd = "sudo fsarchiver -A -Z " + str(compression_level) + \
                " -s " + str(block_count) + \
                " -j " + str(thread_count) + " savefs " + path + \
                "/" + filename + " " + target
            #print ("my_cmd = ",my_cmd)
        elif archive_name:
            position = self.archive_position.get_active_text()
            destination = self.destination_filesystem.get_active_text()
            my_cmd = "sudo fsarchiver restfs " + archive_name + \
                " id=" + position + ",dest=" + destination
        print ("my_cmd = ",my_cmd)
        self.my_cmd = my_cmd
        self.archiver_busy_flag = 1
        t = threading.Thread(target=self.do_my_cmd)
        t.start()
        start_time = time.strftime('%H:%M:%S')
        num_sec = 0
        while self.archiver_busy_flag:
            while Gtk.events_pending():
                Gtk.main_iteration_do(False)
            self.status_label.set_text("Start: " + start_time + " Run time: " + display_time(num_sec, 3) + " running")
            time.sleep(1)
            num_sec += 1

        if self.archiver_busy_flag == 0:
            self.status_label.set_text("Start: " + start_time + " Run time: " + display_time(num_sec, 3) + " Done !")
            time.sleep(1)
            while printdone == 0:
                print ("fsarchiver commando : " + self.status_label.get_text())
                printdone = 1
        #

    # ---------------------------------

    def do_my_cmd(self):
        global printdone
        os.system(self.my_cmd)
        self.archiver_busy_flag = 0
        printdone = 0

    # ---------------------------------

    def show_info_window(self, widget):
        target = self.restore_archive_filename.get_filename()
        print ("Gearchiveerde informatie van bestand = ", target)
        my_cmd = 'sudo fsarchiver archinfo ' + target + ' 2>&1'
        archinfo_msg = ''
        print ("archinfo cmd = ", my_cmd)
        archinfo = os.popen(my_cmd, 'r')
        line = archinfo.readline()
        print (line)
        knt = 1
        while line:
            archinfo_msg = archinfo_msg + line
            line = archinfo.readline()
            print (line)
            knt += 1
            if knt == 3:
                words = line.split(' ')
                try:
                    archive_filecount = int(words[2])
                    for i in range(archive_filecount):
                        self.archive_position.append_text(str(i + 100))
                except:
                    continue
        b = self.archive_info.get_buffer()
        b.set_text(archinfo_msg)
        self.info_window.show_all()

    # ---------------------------------

    def get_device_labels(self):
        blkid = os.popen('sudo blkid -c /dev/null', 'r')
        dev_list = {}
        line = blkid.readline()
        while (line):
            mine = {}
            line = line.replace('/dev', 'DEVICE=/dev')
            line = line.replace(':', ' ')
            line = line.split(' ')
            for l in line:
                if l.strip():
                    m = l.split('=')
                    m[1] = m[1].replace('"', ' ')
                    mine[m[0]] = m[1].strip()
                break
            try:
                x = mine['LABEL']
            except:
                x = 'none'
            dev_list[mine['DEVICE']] = x
            line = blkid.readline()
        return dev_list

    # ---------------------------------

    def set_default_archive_filename(self, widget):
        f = self.fs_to_archive.get_active_text()
        self.create_archive_filename.set_text(self.dev_list[f])

    # ===============================


if __name__ == "__main__":
    app = ArchiverGUI()
    Gtk.main()