Source code for kvalikirstu2.folder_backup
"""A module for creating backups of folders for reverting changes to the study folder."""
import os
import shutil
from kvalikirstu2 import utils
[docs]class FolderTimestamp:
""" Folder timestamp for a backup.
:var int index: The index of the timestamp.
:var float timestamp: The unix timestamp.
"""
def __init__(self, index, timestamp):
"""FolderTimestamp contructor.
:param index: The index of the folder.
:param timestamp: Unix timestamp.
"""
self.index = index
self.timestamp = timestamp
[docs]def get_timestamps(folder, max_backups):
"""Get time stamps for a folder.
:param str folder: Folder path to get backups for.
:param max_backups: Maximum number of backups.
"""
backup_dir = _get_backup_folder(folder)
timestamps = []
for index in range(max_backups):
folder_path = _get_folder_name(index, backup_dir)
if os.path.exists(folder_path):
timestamp = utils.folder_modified_timestamp(folder_path)
timestamps.append(FolderTimestamp(index, timestamp))
return timestamps
[docs]def backup_folder(folder, max_folder_size, max_backups):
"""Create a backup of the folder.
:param str folder: The folder path.
:param max_folder_size: The maximum size of the folder.
:param max_backups: The maximum number of backups.
"""
if not should_backup_folder(folder, max_folder_size, max_backups):
return
new_folder = _get_new_folder_name(folder, max_backups)
if os.path.exists(new_folder):
shutil.rmtree(new_folder)
os.makedirs(new_folder)
for file in os.listdir(folder):
path = os.path.join(folder, file)
if os.path.isfile(path):
shutil.copy(path, os.path.join(new_folder, file))
elif file != _backup_folder_name():
shutil.copytree(path, os.path.join(new_folder, file))
[docs]def clear_backups(folder):
"""Clear old backups for the folder.
:param folder: The path of the folder to clear backups for.
"""
backup_dir = _get_backup_folder(folder)
if os.path.exists(backup_dir):
shutil.rmtree(backup_dir)
[docs]def restore_backup(folder, index):
"""Restore backup with index.
:param str folder: The folder to get a backup for.
:param int index: The index that chooses the timestamp to restore from.
"""
backup_dir = _get_backup_folder(folder)
curr_backup_folder = _get_folder_name(index, backup_dir)
old_folders = []
for file in os.listdir(folder):
path = os.path.join(folder, file)
if file != _backup_folder_name():
if os.path.isfile(path):
os.unlink(path)
else:
new_name = utils.get_temp_path(folder, "")
old_folders.append(new_name)
os.rename(path, new_name)
for file in os.listdir(curr_backup_folder):
path = os.path.join(folder, file)
backup_path = os.path.join(curr_backup_folder, file)
if os.path.isfile(backup_path):
shutil.copy2(backup_path, path)
else:
shutil.copytree(backup_path, path)
for path in old_folders:
shutil.rmtree(path)
def _calculate_folders(folder):
"""Calculates the number of folders inside a folder.
:param folder: The path of the folder.
"""
count = 0
for file in os.listdir(folder):
if os.path.isdir(os.path.join(folder, file)):
count += 1
return count
def _get_backup_folder(folder):
"""Gets the path for the backup folder.
:param folder: The path of the folder.
"""
return os.path.join(folder, _backup_folder_name())
def _backup_folder_name():
"""Gets the name for the backup folder.
"""
return 'backup'
def _get_next_index(folder, backup_path, max_backups):
"""Gets the next index to use for the next backup.
:param folder: The path of the folder.
:param backup_path: That path of the backup folder.
:param max_backups: The maximum number of backups.
"""
folder_count = _calculate_folders(backup_path)
if folder_count < max_backups:
return folder_count
timestamps = get_timestamps(folder, max_backups)
lowest_ts = timestamps[0].timestamp
lowest_index = 0
for index, timestamp in enumerate(timestamps):
if timestamp.timestamp < lowest_ts:
lowest_index = index
lowest_ts = timestamp.timestamp
return lowest_index
def _get_folder_name(index, backup_dir):
"""Get backup folder name for index.
:param index: The index of the folder.
:param backup_dir: The path of the directory used for storing backups.
"""
return os.path.join(backup_dir, str(index))
def _get_new_folder_name(folder, max_backups):
"""Get the backup folder name for a given folder.
:param folder: The path of the folder that is being backed up.
:param max_backups: The maximum number of backups.
"""
backup_path = _get_backup_folder(folder)
_init_backup_folder(backup_path)
index = _get_next_index(folder, backup_path, max_backups)
return _get_folder_name(index, backup_path)
def _init_backup_folder(folder):
"""Initializes the backup folder.
:param folder: The path of the backup folder.
"""
if not os.path.exists(folder):
os.makedirs(folder)
[docs]def folder_too_large(folder, max_folder_size):
"""Is the folder too large to be backed up?
:param folder: The path of the folder.
:param max_folder_size: The maximum size of the folder in bytes.
:return: True/False based on whether or not the folder is too large to be backed up.
"""
return _get_folder_size_wo_backup(folder) > max_folder_size
[docs]def should_backup_folder(folder, max_folder_size, max_backups):
"""Should the folder be backed up?
:param folder: The path of the folder.
:param max_folder_size: The maximum size of the folder being backed up.
:param max_backups: The maximum number of backups to store.
"""
if folder_too_large(folder, max_folder_size):
return False
timestamp = utils.folder_modified_timestamp(folder)
timestamps = get_timestamps(folder, max_backups)
filtered = [ts for ts in timestamps if ts.timestamp == timestamp]
return not filtered
def _get_folder_size_wo_backup(folder_path):
"""Gets the size of a folder, excluding the folder used for backup.
:param folder_path: The path of the folder to be examined.
"""
size = 0
for file in os.listdir(folder_path):
path = os.path.join(folder_path, file)
if os.path.isfile(path):
size += os.path.getsize(path)
elif os.path.isdir(path) and file != _backup_folder_name():
size += utils.get_folder_size(path)
return size