#!/usr/bin/env python3

# Copyright (C) 2013-2019 Jonathan Vasquez <jon@xyinn.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys
import os

from subprocess import call
from subprocess import check_output
from subprocess import CalledProcessError

zfs_cmd = "/sbin/zfs"
btrfs_cmd = "/sbin/btrfs"

# Enables the user to efficiently clean the snapshots in their pool.
class Cleaner:
    def setup_fs_manager(self):
         # Make sure the following parameters were set before we continue
        fstype = paramUtil.get_filesystem_type()

        if not fstype:
            Tools.die("No filesystem type was specified.")

        if not paramUtil.get_pool_name():
            Tools.die("No pool name was specified.")

        if fstype == "zfs":
            self.fs_manager = ZfsManager()
        elif fstype == "btrfs":
            self.fs_manager = BtrfsManager()
        else:
            Tools.die("The filesystem you've specified is not supported!")

        tag = paramUtil.get_tag()

        if tag:
            self.fs_manager.set_tag(tag)

        # Extracts the pool information needed given the filesystem type being used
        self.fs_manager.set_pool_info(paramUtil.get_pool_name())

        # Updates the time slices immediately, this also serves
        # as a check to see if the user wrote in the date in the
        # correct format: month-date-year-time
        DateManager.update_date_slices()

    # Main
    def start(self):
        # Sets up the filesystem manager, and a few other things such as time slices.
        self.setup_fs_manager()

        # Retrieves the snapshots to exclude if needed.
        if paramUtil.get_exclude_filename():
            self.fs_manager.populate_excluded_snapshots_set()

        print("Today's Date : " + DateManager.date_today)
        print("Slice Date   : " + DateManager.date_slice)
        print("Pool         : " + self.fs_manager.pool)
        print("")

        # Retrieves the snapshots in our given pool
        self.fs_manager.get_pool_snapshots()

        # Check each snapshot and if it is older than the slice date,
        # add it to our removal queue. Snapshots that are listed in the exclude
        # file will be excluded and will not be added to the removal queue.
        self.fs_manager.check_snapshots()

        # Print the snapshots that will be removed/excluded
        if self.fs_manager.queue:
            if paramUtil.should_show_removals():
                self.fs_manager.print_queue()

            if paramUtil.should_show_excluded():
                self.fs_manager.print_excluded_queue()

            print("Amount of Snapshots to Remove: " + str(self.fs_manager.get_queue_size()))
            print("Amount of Snapshots to Exclude: " + str(self.fs_manager.get_excluded_queue_size()))
            print("")

        if self.fs_manager.queue:
            if not paramUtil.is_force():
                choice = input("Do you want to remove the above snapshots? [y/N]: ")

                # Remove the snapshots that are in the slicer
                if choice == 'y' or choice == 'Y':
                    self.fs_manager.destroy_snapshots()

                elif choice == 'n' or choice == 'N' or not choice:
                    print("\nNo snapshots will be removed. Exiting.")
                    quit()
                else:
                    Tools.die("Invalid Option. Exiting.")

            # If the force flag was enabled (maybe script mode), then just remove
            # the snapshots without asking for confirmation.
            else:
                self.fs_manager.destroy_snapshots()
        else:
            print("The pool is already clean. Exiting.")

# Retrieves and retains parameter data
class ParameterUtility:
    def __init__(self):
        # Don't ask for confirmation when deleting snapshots?
        self._force = False

        # What type of filesystem snapshots will we be managing? (zfs or btrfs)
        self._fstype = ""

        # Label/Tag.
        self._tag = ""

        # Pool name
        self._pool_name = ""

        # Minimum number of snapshots to keep
        self._minimum_queue_size = 1000

        # Number of snapshots to delete per buffer
        self.batch_flush_point = -1

        # Ignore the minimum queue size
        self._flush_queue = False

        # Show snapshots that will be deleted
        self._show_pending_removals = False

        # Show excluded snapshots
        self._show_excluded_snapshots = False

        # Exclude snapshots in this file
        self._exclude_filename = ""

    def get_filesystem_type(self):
        return self._fstype

    def get_tag(self):
        return self._tag

    def get_pool_name(self):
        return self._pool_name

    def is_force(self):
        return self._force

    def get_exclude_filename(self):
        return self._exclude_filename

    def set_filesystem_type(self, fstype):
        self._fstype = fstype

    def set_tag(self, tag):
        self._tag = tag

    def set_pool_name(self, pool_name):
        self._pool_name = pool_name

    def set_force(self, flag):
        self._force = flag

    def set_minimum_queue_size(self, size):
        self._minimum_queue_size = size

    def get_minimum_queue_size(self):
        return self._minimum_queue_size

    def set_flush_queue(self, value):
        self._flush_queue = value

    def should_flush_queue(self):
        return self._flush_queue

    def set_show_removals(self, value):
        self._show_pending_removals = value

    def set_show_excluded(self, value):
        self._show_excluded_snapshots = value

    def should_show_removals(self):
        return self._show_pending_removals

    def should_show_excluded(self):
        return self._show_excluded_snapshots

    def set_exclude_filename(self, filename):
        self._exclude_filename = filename

    def get_batch_flush_point(self):
        return self.batch_flush_point

    # Checks parameters and running user
    def parse_parameters(self):
        user = check_output(["whoami"], universal_newlines=True).strip()

        if user != "root":
            Tools.die("This program must be ran as root")

        arguments = sys.argv[1:]

        # clean_snapshots -f -p <pool> -t <fstype> -d <date> -l <tag label> -e <snapshot exclude file>
        # clean_snapshots -f -p tank -t zfs -d 2014-06-09-0000 -l HOURLY
        if len(arguments) >= 1:
            for i in range(len(arguments)):
                # Sets the force flag
                if arguments[i] == "-f":
                    self.set_force(True)

                # Flush Queue [Ignores the minimum queue size]
                if arguments[i] == "-i":
                    self.set_flush_queue(True)

                # Sets the type of snapshots we will be cleaning (zfs or btrfs)
                if arguments[i] == "-t":
                    try:
                        self.set_filesystem_type(arguments[i+1])
                    except IndexError:
                        Tools.die("No filesystem type was passed to -t")

                # Sets the top level dataset name
                elif arguments[i] == "-p":
                    try:
                        self.set_pool_name(arguments[i+1])
                    except IndexError:
                        Tools.die("No pool name was passed to -p")

                # Sets the slice date
                elif arguments[i] == "-d":
                    try:
                        DateManager.date_slice = arguments[i+1]
                    except IndexError:
                        Tools.die("No date was passed to -d")

                # Sets the label tag
                elif arguments[i] == "-l":
                    try:
                        self.set_tag(arguments[i+1])
                    except IndexError:
                        Tools.die("No tag was passed to -l")

                # Sets the minimum number of snapshots to keep
                elif arguments[i] == "-m":
                    try:
                        self.set_minimum_queue_size(int(arguments[i+1]))
                    except IndexError:
                        Tools.die("No minimum queue size was passed to -m")

                elif arguments[i] == "-n":
                    try:
                        self.batch_flush_point = int(arguments[i+1])
                    except IndexError:
                        Tools.die("No maximum number of snapshots to delete per buffer amount was passed to -n")

                # Displays the help/usage message
                elif arguments[i] == "-h":
                    Tools.print_usage()
                    quit(0)

                # Shows snapshots that will be removed
                elif arguments[i] == "-s":
                    self.set_show_removals(True)

                # Shows snapshots that will be excluded
                elif arguments[i] == "-x":
                    self.set_show_excluded(True)

                # Sets exclude filename
                elif arguments[i] == "-e":
                    try:
                        if not os.path.exists(arguments[i+1]):
                            Tools.die("The specified exclude file doesn't exist: " + arguments[i+1])

                        self.set_exclude_filename(arguments[i+1])
                    except IndexError:
                        Tools.die("Exclude filename was not set")
        else:
            Tools.print_usage()
            Tools.die("You must pass the pool you want to clean. See the above examples.")

# Provides utility functions that can be used by any class
class Tools:
    @classmethod
    # Runs a command and gets back the output
    def run(cls, cmd):
        return check_output(cmd, shell=True, universal_newlines=True).strip()

    @classmethod
    # Displays a message and quits with an error code of 1
    def die(cls, message):
        print(message)
        quit(1)

    @classmethod
    # Prints usage information
    def print_usage(cls):
        print("Flags")
        print("=====")
        print("-d       - The slice date that you want to use as your end point for snapshot deletions")
        print("-f       - Should confirmation be prompted before deleting the snapshots? Used primarily for cron")
        print("-h       - Displays this help message")
        print("-i       - Ignore minimum number of snapshots")
        print("-l       - The label of the snapshots that should be cleaned (supports regex)")
        print("-m       - The minimum amount of snapshots to keep")
        print("-p       - The pool you want to clean")
        print("-s       - Show snapshots that will be removed")
        print("-x       - Show snapshots that will be excluded")
        print("-t       - The type of filesystem that should be cleaned")
        print("-e       - Excludes the list of snapshots in this file (one snapshot per line)")
        print("-n       - Number of snapshots to delete per round")
        print("")
        print("Examples")
        print("=====")
        print("Cleans target pool asking for confirmation with a default slice date of 30 days prior to today:")
        print("# clean_snapshots -p <pool> -t zfs \n")
        print("Cleans target pool while deleting 2000 snapshots at a time.")
        print("# clean_snapshots -p <pool> -t zfs -n 2000\n")
        print("Cleans target pool asking for confirmation with a slice date of <date>:")
        print("# clean_snapshots -p <pool> -t zfs -d <date>\n")
        print("Cleans target pool asking for confirmation with a slice date of <date> and with a tag of <tag>:")
        print("# clean_snapshots -p <pool> -t zfs -d <date> -l <tag>\n")
        print("Cleans target pool not asking for confirmation with a default slice date of 30 days prior to today:")
        print("# clean_snapshots -f -p <pool> -t btrfs\n")
        print("Cleans target pool asking for confirmation and sets a minimum number of snapshots to keep (default 1000):")
        print("# clean_snapshots -p <pool> -t zfs -m 2000\n")
        print("Cleans target pool asking for confirmation and ignores minimum number of snapshots to keep:")
        print("# clean_snapshots -p <pool> -t zfs -i\n")
        print("Excludes the snapshots in the file:")
        print("# clean_snapshots -p <pool> -t zfs -e <file>")
        print("")

# Provides date managing tools
class DateManager:
    @classmethod
    def __init__(cls):
        cls.date_today = cls.get_todays_date()
        cls.date_slice = cls.get_default_date()
        cls.date_slice_year = ""
        cls.date_slice_month = ""
        cls.date_slice_day = ""
        cls.date_slice_time = ""

    @classmethod
    # Updates the date slices
    def update_date_slices(cls):
        try:
            cls.date_slice_year = cls.get_slice_year()
            cls.date_slice_month = cls.get_slice_month()
            cls.date_slice_day = cls.get_slice_day()
            cls.date_slice_time = cls.get_slice_time()
        except IndexError:
            Tools.die("Date must be in YYYY-mm-dd-MMSS format! Example: " + cls.date_today)

    @classmethod
    # Gets the year from date_slice
    def get_slice_year(cls):
        return int(cls.date_slice.split("-")[0])

    @classmethod
    # Gets the month from date_slice
    def get_slice_month(cls):
        return int(cls.date_slice.split("-")[1])

    @classmethod
    # Gets the day from date_slice
    def get_slice_day(cls):
        return int(cls.date_slice.split("-")[2])

    @classmethod
    # Gets the time from date_slice
    def get_slice_time(cls):
        return int(cls.date_slice.split("-")[3])

    @classmethod
    # Gets today's date
    def get_todays_date(cls):
        cmd = 'date +%F-%H%M'
        return Tools.run(cmd)

    @classmethod
    # Prints the date slices individually
    def print_date_slices(cls):
        print("Slice Month: " + str(cls.date_slice_month))
        print("Slice Day: " + str(cls.date_slice_day))
        print("Slice Year: " + str(cls.date_slice_year))
        print("Slice Time: " + str(cls.date_slice_time))

    @classmethod
    # Returns the default date (30 days before today)
    def get_default_date(cls):
        date_parts = cls.date_today.split("-")
        new_month = int(date_parts[1]) - 1

        # If the month ends up being 0 (January - 1), then roll back to december
        # and also roll back the calendar year by 1.
        if new_month == 0:
            new_month = 12
            new_year = int(date_parts[0]) - 1
        else:
            new_year = int(date_parts[0])

        firstHalf = str(new_year) + "-"
        secondHalf = str(new_month) + "-" + date_parts[2] + "-" + date_parts[3]

        # If the month is less than 10, then pad the month with a 0
        if new_month < 10:
            def_date =  firstHalf + "0" + secondHalf
        else:
            def_date = firstHalf + secondHalf

        return def_date

# Provides common functionality between things that cleaning a zfs and btrfs snapshot require.
class FSManager:
    def __init__(self):
        # List of snapshots in our given pool
        self.snapshots = []

        # List of snapshots that will be deleted
        self.queue = []
        
        # Number of snapshots deleted. Used for progress indicator.
        self.deleted_snapshot_count = 0
        
        # Pool
        self.pool = ""

        # Tag
        self.tag = ""

        # Full list of excluded snapshots (contains all the snapshots from our file)
        self.excluded_snapshots_from_file = set()

        # List of excluded snapshots (That are only related to this pool)
        self.excluded_snapshots = set()

    # Checks all the snapshots in the queue and if older adds them to our deletion queue
    def check_snapshots(self):
        snapshots_queue_size = len(self.snapshots)
        minimum_queue_size = paramUtil.get_minimum_queue_size()
        maximum_amount_to_remove = len(self.snapshots) - paramUtil.get_minimum_queue_size()

        # Print the snapshots that will be removed
        if not paramUtil.should_flush_queue():
            if minimum_queue_size >= snapshots_queue_size:
                print("Current Number of Snapshots: " + str(snapshots_queue_size))
                print("Minimum Number of Snapshots to Keep: " + str(minimum_queue_size) + "\n")
                print("No snapshots will be removed since you are still within the "
                      "minimum amount of snapshots to keep limit.")
                exit(0)

        for snap in self.snapshots:
            snapshots_size = len(self.snapshots)

            # Make sure that we only add snapshots to the removal queue up to our maximum
            if not paramUtil.should_flush_queue():
                if self.get_queue_size() >= maximum_amount_to_remove:
                    # We done ;D
                    return

            self.slicer(snap)

    # Removes the target snapshot
    def destroy(self, a_snap, cmd):
        print("Removing: " + a_snap)
        Tools.run(cmd)

    # Remove the snapshots
    def destroy_snapshots(self):
        for snap in self.queue:
            self.destroy(snap)

    # Retrieves all the snapshots for the given pool
    def get_pool_snapshots(self, cmd):
        if self.tag:
            cmd = cmd + " | grep " + self.tag

        try:
            results = Tools.run(cmd)

            if results:
                for snap in results.split("\n"):
                    self.snapshots.append(snap)
        except CalledProcessError:
            pass

    # Prints out the snapshots in the given pool
    def print_pool_snapshots(self):
        if self.snapshots:
            for snap in self.snapshots:
                print("Snapshot: " + snap)
        else:
            print("No snapshots available!")

    # Sets the tag
    def set_tag(self, tag):
        self.tag = tag

    # Adds a snapshot to our removal queue
    def add_to_queue(self, a_snap):
        self.queue.append(a_snap)

    # Retrieves the snapshot removal queue
    def get_queue(self):
        return self.queue

    # Returns the queue size
    def get_queue_size(self):
        return len(self.queue)

    # Returns the size of the excluded snapshots queue
    def get_excluded_queue_size(self):
        return len(self.excluded_snapshots)

    # Prints the snapshot removal queue
    def print_queue(self):
        print("These snapshots are PENDING REMOVAL: ")
        print("----------------")

        if len(self.queue) == 0:
            print("No snapshots will be removed.")
        else:
            for snap in self.queue:
                print(snap)

        print("")

    def print_excluded_queue(self):
        print("These snapshots are EXCLUDED from REMOVAL: ")
        print("----------------")

        if len(self.excluded_snapshots) == 0:
            print("No snapshots are excluded.")
        else:
            for snap in self.excluded_snapshots:
                print(snap)

        print("")

    # Adds a snapshot to our removal list if it passes the slice date
    def slicer(self, a_snap, date_split):
        year = int(date_split[0])
        month = int(date_split[1])
        day = int(date_split[2])
        time = int(date_split[3])

        should_add_to_queue = False

        if year < DateManager.date_slice_year:
            # Snapshot is older by at least a year
            should_add_to_queue = True
        elif year == DateManager.date_slice_year:
            if month < DateManager.date_slice_month:
                # Snapshot is older by at least a month
                should_add_to_queue = True
            elif month == DateManager.date_slice_month:
                if day < DateManager.date_slice_day:
                    # Snapshot is older by at least a day
                    should_add_to_queue = True
                elif day == DateManager.date_slice_day:
                    if time <= DateManager.date_slice_time:
                    # Snapshot is the same day as today but is older or equal to the slice time.
                        should_add_to_queue = True

        if should_add_to_queue:
           # If the snapshot is in our exclude list, skip it.
            if self.should_exclude_snapshot(a_snap):
                self.excluded_snapshots.add(a_snap)
            else:
                self.add_to_queue(a_snap)

    # Determines whether or not the given snapshot name should be excluded from removal
    def should_exclude_snapshot(self, snapshot):
        return snapshot in self.excluded_snapshots_from_file

    # Retrieves the list of snapshots
    def populate_excluded_snapshots_set(self):
        try:
            with open(paramUtil.get_exclude_filename(), "r") as exclude_list:
                for snapshot_to_exclude in exclude_list:
                    self.excluded_snapshots_from_file.add(snapshot_to_exclude.strip())
        except Exception:
            Tools.die("An error occurred while reading the file. Aborting.")

# Provides zfs specific attributes that is required to clean zfs snapshots
class ZfsManager(FSManager):
    def __init__(self):
        super().__init__()
        self.base_dataset = ""
        self.batch_of_snapshots = []
        
        # Remove 100 snapshots per buffer. This is to prevent us from reaching
        # the ARG_MAX limits in shell/exec(). Having too high of a snapshot
        # deleting amount seems to cause zfs to lock up in some cases.
        self.batch_flush_point = paramUtil.get_batch_flush_point()

        if self.batch_flush_point == -1:
            self.batch_flush_point = 100

    def slicer(self, a_snap):
         # Left side = dataset, Right Side = date [0, 1]
        at_split = a_snap.split("@")

        # Splits the right side into month/date/year/time
        date_split = at_split[1].split("-")

        super().slicer(a_snap, date_split)

    # Destroys the snapshots
    def destroy_snapshots(self):
        self.build_batch_and_clean()

    # Deletes all the snapshots currently in batch queue and clears out the queue for re-use
    def destroy_and_clear(self):
        self.destroy(self.return_prepared_list(self.batch_of_snapshots))
        
        self.deleted_snapshot_count = self.deleted_snapshot_count + len(self.batch_of_snapshots)
        completed_percent = self.calculate_completion(self.deleted_snapshot_count, len(self.queue))
        print("{0:3d}% Completed. [".format(completed_percent) + str(self.deleted_snapshot_count) + "/" + str(len(self.queue)) + "]" + " @ [" + self.base_dataset + "]")
        
        self.batch_of_snapshots.clear()

    # Returns the percent completion of the snapshot removal
    def calculate_completion(self, number_of_deleted_snapshots, total_snapshots):
        return int(round((number_of_deleted_snapshots / total_snapshots) * 100))
        
    # Destroys the prepared batch (Contains base dataset and a comma-delimited list of snapshot dates)
    def destroy(self, prepared_batch):
        cmd = zfs_cmd + ' destroy ' + prepared_batch
        Tools.run(cmd)

    def set_pool_info(self, pool):
        self.pool = pool

    # Retrieves the short name of the snapshot (The part after the @)
    def get_snapshot_shortname(self, snapshot):
        splitted = snapshot.split("@")
        return splitted[1]

    # Returns the dataset of this snapshot
    def get_snapshot_dataset(self, snapshot):
        splitted = snapshot.split("@")
        return splitted[0]

    # Sets the base dataset and adds the snapshot to the queue
    def set_base_and_queue(self, snapshot):
        self.base_dataset = self.get_snapshot_dataset(snapshot)
        print("\nCleaning snapshots for " + self.base_dataset + "...\n")
        self.batch_of_snapshots.append(snapshot)

    # Prepares a list of snapshots to be deleted starting with the base dataset name
    # followed by a comma-delimited list of snapshots to delete
    def return_prepared_list(self, list_to_prepare):
        return ','.join(list_to_prepare)

    # Builds the batch list for the snapshots we should clean
    # and calls the functions necessary to actually delete the snapshots
    def build_batch_and_clean(self):        
        for i in range(len(self.queue)):
            if len(self.batch_of_snapshots) != self.batch_flush_point:
                if len(self.batch_of_snapshots) == 0:
                    # Set the base dataset so that we can make sure that every subsequent
                    # snapshot is part of the same dataset.
                    self.set_base_and_queue(self.queue[i])
                else:
                    current_dataset = self.get_snapshot_dataset(self.queue[i])

                    # If the current dataset is the same as our base dataset, then
                    # this is actually going to be the next dataset that we delete
                    # when we flush our batch. If it isn't, then this means that we've
                    # reached the end of all the snapshots that this dataset has
                    # (since the dataset queue list is sorted), and that we should clean,
                    # the last bit of snapshots for the dataset, reset the base dataset,
                    # and start collecting snapshots for the next flush.
                    if current_dataset == self.base_dataset:
                        short_name = self.get_snapshot_shortname(self.queue[i])
                        self.batch_of_snapshots.append(short_name)
                    else:
                        #print("Dataset end reached. Flushing...")
                        self.destroy_and_clear()
                        self.set_base_and_queue(self.queue[i])
            else:
                #print("Buffer is full! Flushing...")
                self.destroy_and_clear()

                # Add the current dataset into the snapshot list so it can be cleared
                # during the next flush. This snapshot is still part of our existing base_dataset.
                self.batch_of_snapshots.append(self.queue[i])

        # If the buffer didn't become full but we already went through all the snapshots then flush now
        if len(self.batch_of_snapshots) != 0:
            #print("Flushing the last remaining snapshots...")
            self.destroy_and_clear()

            if len(self.batch_of_snapshots) == 0:
                print("\nThe pool has been successfully cleaned!")
            else:
                Tools.die("\nThere was an error cleaning the pool!")

    # Retrieves all the snapshots for the given pool
    def get_pool_snapshots(self):
        cmd = zfs_cmd + ' list -t snapshot -H -o name -s name | grep ^' + self.pool + '[@/]'
        super().get_pool_snapshots(cmd)

# Provides btrfs specific attributes that are required to clean btrfs snapshots
class BtrfsManager(FSManager):
    def __init__(self):
        super().__init__()
        self.top_level = ""
        self.snapshots_dir = ""

    def slicer(self, a_snap):
        # Split so that we can find the date part easier
        at_split = a_snap.split("/")

        # Splits the date part into month/date/year/time
        date_split = at_split[2].split("-")

        super().slicer(a_snap, date_split)

    def destroy(self, a_snap):
        snapshot_to_remove = self.top_level + "/" + a_snap
        cmd = btrfs_cmd + ' subvol del ' + snapshot_to_remove
        super().destroy(a_snap, cmd)

    def destroy_snapshots(self):
        super().destroy_snapshots()

        # Remove the empty folders
        self.remove_empty_folders()

    # Remove all empty folders (dirty solution)
    def remove_empty_folders(self):
        cmd = 'rmdir ' + self.snapshots_dir + '/* 2> /dev/null'
        call(cmd, shell=True)

    # Retrieves all the snapshots for the given pool
    def get_pool_snapshots(self):
        cmd = btrfs_cmd + ' subvol list ' + self.snapshots_dir + ' -o | cut -d " " -f 9'
        super().get_pool_snapshots(cmd)

    # Extracts the required information from the -p flag
    def set_pool_info(self, pool):
        # Strip trailing / from end of 'pool' parameter path since we do some further
        # breakdowns later on to extract the correct paths from this single parameter.
        # Example: /mnt/toplevel/tank/ -> /mnt/toplevel/tank
        pool = pool.rstrip("/")

        self.pool = os.path.basename(pool)
        self.top_level = os.path.dirname(pool)
        self.snapshots_dir = self.top_level + "/" + self.pool + "/snapshots"

        if not self.pool:
            Tools.die("There was an error retrieving the pool name from the path specified.")

    def print_pool_info(self):
        print("Pool: " + self.pool)
        print("Top Level: " + self.top_level)
        print("Snapshots Dir: " + self.snapshots_dir)

if __name__ == "__main__":
    # Starts up the following class so that the static variables within it are initialized.
    DateManager()

    cleaner = Cleaner()
    paramUtil = ParameterUtility()

    paramUtil.parse_parameters()
    cleaner.start()
