import re import gzip import sqlite3 from crypt import * import pathlib class Backup: key = None compressMinSize = "50M" save_location = None def __init__(self, key, bdd): self.key = key self.bdd = DataBase(bdd) def recurse(self, path): min_size = parse_size(self.compressMinSize) files = [] for f in os.listdir(path): uri = os.path.join(path, f) if os.path.isfile(uri): size = os.path.getsize(uri) print(f + " : ", human_size(size)) if size > min_size: enc = crypt(compress(uri), self.key) else: enc = crypt(uri, self.key) files.append({'name': f, 'path': uri}) save(enc, os.path.join(self.save_location, f + ".enc")) elif os.path.isdir(uri): self.recurse(uri) self.bdd.add(files) def compress(file): if type(file) is str: infile = open(file, 'rb') elif type(file) is io.BufferedRandom or tempfile.SpooledTemporaryFile: file.seek(0) infile = file compressed_file = tempfile.SpooledTemporaryFile() with gzip.open(compressed_file, 'wb') as zipfile: while chunk := infile.read(64 * 1024): zipfile.write(chunk) return compressed_file def uncompress(file): if type(file) is io.BufferedRandom or tempfile.SpooledTemporaryFile: file.seek(0) decompressed_file = tempfile.SpooledTemporaryFile() with gzip.open(file, 'rb') as zipfile: while chunk := zipfile.read(64 * 1024): decompressed_file.write(chunk) return decompressed_file def crypt(file, key): encrypted_file = tempfile.SpooledTemporaryFile() encrypt_file(key, file, encrypted_file) return encrypted_file def uncrypt(file, key): decrypted_file = tempfile.SpooledTemporaryFile() decrypt_file(key, file, decrypted_file) return decrypted_file def save(file, name): if not os.path.isdir(os.path.dirname(name)): os.mkdir(os.path.dirname(name)) if type(file) is io.BufferedRandom or tempfile.SpooledTemporaryFile: file.seek(0) with open(name, 'wb') as save: while chunk := file.read(64 * 1024): save.write(chunk) else: print("Unable to save " + str(file) + " of type " + str(type(file))) return class DataBase: def __init__(self, base_file): self.conn = sqlite3.connect(base_file) self.__create_table() def __del__(self): self.conn.commit() self.conn.close() def __create_table(self): cursor = self.conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS files( id INTEGER PRIMARY KEY UNIQUE NOT NULL, name TEXT, path TEXT ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS crypt( id INTEGER PRIMARY KEY UNIQUE NOT NULL, compressed INTEGER ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS content( id INTEGER PRIMARY KEY UNIQUE NOT NULL, files_id INTEGER, crypt_id INTEGER, isdir INTEGER, CONSTRAINT content_files_FK FOREIGN KEY (files_id) REFERENCES files(id), CONSTRAINT content_crypt_FK FOREIGN KEY (crypt_id) REFERENCES crypt(id) ) """) self.conn.commit() def add(self, list_file, compressed=False): isdir = True if len(list_file) > 0 else False cursor = self.conn.cursor() cursor.execute("""SELECT IFNULL(max(id), 0) FROM crypt""") crypt_id = cursor.fetchone()[0] cursor.execute("""SELECT IFNULL(max(id), 0) FROM content""") content_id = cursor.fetchone()[0] cursor.execute("""SELECT IFNULL(max(id), 0) FROM files""") files_id = cursor.fetchone()[0] for file in list_file: cursor.execute("""INSERT INTO files VALUES(?, ?, ?)""", (files_id, file['name'], file['path'])) files_id += 1 cursor.execute("""INSERT INTO crypt VALUES(?, ?)""", (crypt_id, compressed)) cursor.execute("""INSERT INTO content VALUES(?, ?, ?, ?)""", (content_id, files_id, crypt_id, isdir)) return crypt_id def human_size(size, decimal_places=0): for unit in ['B', 'K', 'M', 'G', 'T']: if size < 1024.0: break size /= 1024.0 return f"{size:.{decimal_places}f}{unit}" def parse_size(size): units = {"B": 1, "K": 2**10, "M": 2**20, "G": 2**30, "T": 2**40} if size[-1].isdigit(): size = size + 'K' number, unit = re.match(r"([0-9]+)([BKMGT])", size, re.I).groups() return int(float(number)*units[unit])