#!/usr/bin/env python2

# this script was written by somebody else, i have no idea how well it works, but people like clicky things
# i especially dislike the sudo workaround, and i didn't really read the code, but i tested the gui,
# it was working, i like it, so yea, here you go
#
# please also note that this file is not under the LICENSE of the rest of the code (for now)

# import all the stuff the gui needs
from Tkinter import *
import Tkinter as tk
import tkFileDialog
from Tkinter import _setit
from ttk import *

import os, sys, ttk, subprocess, plistlib, tkMessageBox, traceback

# if we aren't sudoing this
if os.getuid() != 0:
    print "Root permission needed to access /dev block devices."

    sudo_bin = "/usr/bin/sudo"
    if not os.path.exists("/usr/bin/sudo"):
        sudo_bin = "/bin/su --command"

    python = sys.executable
    script = sys.argv[0]

    command = [sudo_bin, python, script]
    os.execlp(sudo_bin, *command )
else:
    pass

# change working path to sky3ds folder in case it is run with terminal in a separate folder
os.chdir(os.path.dirname(os.path.realpath(__file__)))

# import the backend stuff
sys.path.append("sky3ds")
sys.path.append("third_party/appdirs")
sys.path.append("third_party/progressbar")
import sky3ds
from sky3ds import disk as disk_functions # 'disk' is too generic a name to avoid confusion
from sky3ds import titles, gamecard
from appdirs import user_data_dir


# the good stuff:
root = Tk()

class View(Frame):

    def __init__(self, root):
        Frame.__init__(self, root)

        # Exception catcher. Use this for catching exceptions in the backend code. Works since tk loop is already going and won't be completely broken by an interruption.
        tk.Tk.report_callback_exception = self.exception

        self.root = root

        self.root.title("SKY3DS Diskwriter")

        self.create_widgets()
        self.create_menus()
        self.modify_window()

        controller.check_for_template()

    def create_widgets(self):
        frame_master = Frame(root, padding = 5)

        frame_top_row = Frame(frame_master)

        label_choose_disk = Label(frame_top_row, text = "Choose Disk:")
        label_choose_disk.pack(side=LEFT)

        self.disk_choice = StringVar()
        self.menu_choose_disk = OptionMenu(frame_top_row, self.disk_choice, '')
        self.menu_choose_disk.pack(side = LEFT, fill=BOTH, expand=1)
        self.update_disk_optionmenu()

        self.button_open_disk = Button(frame_top_row, text = "Open Disk", command = controller.open_disk)
        self.button_open_disk.pack(side=RIGHT)

        button_refresh_list = Button(frame_top_row, text = "Refresh List", command = self.update_disk_optionmenu )
        button_refresh_list.pack(side = RIGHT)

        frame_top_row.pack(fill=BOTH, expand=1, pady = 3)

        frame_rom_list = Frame(frame_master)

        rom_table_columns = ["Slot", "Start", "Size", "Type", "Code", "Title", "Save Crypto"]
        self.tree_rom_table = ttk.Treeview(frame_rom_list, height = 10, columns = rom_table_columns, selectmode="extended")
        self.tree_rom_table.pack()

        self.tree_rom_table.heading('#1', text='Slot', anchor=W)
        self.tree_rom_table.heading('#2', text='Start', anchor=W)
        self.tree_rom_table.heading('#3', text='Size', anchor=W)
        self.tree_rom_table.heading('#4', text='Type', anchor=W)
        self.tree_rom_table.heading('#5', text='Code', anchor=W)
        self.tree_rom_table.heading('#6', text='Title', anchor=W)
        self.tree_rom_table.heading('#7', text='Save Crypto', anchor=W)

        self.tree_rom_table.column('#1', stretch=False, minwidth=40, width=40)
        self.tree_rom_table.column('#2', stretch=NO, minwidth=72, width=72)
        self.tree_rom_table.column('#3', stretch=NO, minwidth=72, width=72)
        self.tree_rom_table.column('#4', stretch=NO, minwidth=72, width=72)
        self.tree_rom_table.column('#5', stretch=NO, minwidth=100, width=100)
        self.tree_rom_table.column('#6', stretch=NO, minwidth=200, width=200)
        self.tree_rom_table.column('#7', stretch=NO, minwidth=90, width=90)
        self.tree_rom_table.column('#0', stretch=NO, minwidth=0, width=0) #width 0 to not display it

        frame_rom_list.pack()

        frame_disk_use_info = Frame(frame_master)

        self.disk_name = StringVar()
        label_disk_name = Label(frame_disk_use_info, textvariable = self.disk_name)
        label_disk_name.pack(side=LEFT)

        self.disk_free_space = StringVar()
        label_disk_free_space = Label(frame_disk_use_info, textvariable = self.disk_free_space)
        label_disk_free_space.pack(side=LEFT, padx = 10)

        self.disk_continuous_space = StringVar()
        label_disk_continuous_space = Label(frame_disk_use_info, textvariable = self.disk_continuous_space)
        label_disk_continuous_space.pack(side=LEFT)

        frame_disk_use_info.pack(fill = BOTH, expand = 1, pady = 3)


        separator = Separator(frame_master, orient = HORIZONTAL )
        separator.pack(fill = BOTH, expand = 1, padx = 10)

        frame_write_buttons = Frame(frame_master)

        button_write_rom = Button(frame_write_buttons, text = "Write Rom", command = controller.write_rom )
        button_write_rom.pack(side=LEFT)

        button_delete_rom = Button(frame_write_buttons, text = "Delete Rom", command = controller.delete_rom )
        button_delete_rom.pack(side=LEFT)

        button_backup_rom = Button(frame_write_buttons, text = "Backup Rom", command = controller.backup_rom )
        button_backup_rom.pack(side=LEFT)

        button_write_save = Button(frame_write_buttons, text = "Write Save", command = controller.write_save )
        button_write_save.pack(side=RIGHT)

        button_backup_save = Button(frame_write_buttons, text = "Backup Save", command = controller.backup_save )
        button_backup_save.pack(side=RIGHT)

        frame_write_buttons.pack(fill = BOTH, expand = 1, pady = 5)

        frame_master.pack()

    def update_disk_optionmenu(self):
        disk_list = controller.get_disk_list()
        menu = self.menu_choose_disk['menu']
        # clear all options
        menu.delete(0, 'end')
        # add new
        if not disk_list:
            raise Exception("No Acceptable disks found")
        else:
            for disk in disk_list:
                menu.add_command(label=disk, command=_setit(self.disk_choice,disk))
                self.disk_choice.set(disk)

    def create_menus(self):
        menu_bar = Menu(root)

        menu_file = Menu(menu_bar, tearoff=0)
        menu_bar.add_cascade(label="File", menu=menu_file)
        menu_file.add_command(label="Exit", command=root.quit)

        menu_tools = Menu(menu_bar, tearoff=0)
        menu_bar.add_cascade(label="Tools", menu=menu_tools)
        menu_tools.add_command(label="Update Template", command = lambda: update_template() )
        menu_tools.add_command(label="Update Titles", command = lambda: update_titles() )
        menu_tools.add_separator()
        menu_tools.add_command(label="Format Disk", command = lambda: format_disk() )

        #menu_help = Menu(menu_bar, tearoff=0)
        #menu_bar.add_cascade(label="Help", menu=menu_help)
        #menu_help.add_command(label="Diskwriter Help", command = lambda: help_window() )

        root.config(menu=menu_bar)

    def modify_window(self):
        self.root.resizable(0,0) # stops user from resizing window
        root.withdraw()                                                 # un-draw window
        root.update_idletasks()                                         # Update "reqwidth" and "reqheight" from geometry manager
        x = (root.winfo_screenwidth() - root.winfo_reqwidth()) / 2      # Figures out location based on screen and window size
        y = (root.winfo_screenheight() - root.winfo_reqheight()) / 2    #  "
        root.geometry("+%d+%d" % (x, y))                                # places the window at that loction
        root.deiconify()                                                # redraws window

    def exception(self, *args):
        self.top = Toplevel()
        self.top.title("Something broke.")
        self.err = args[1]

        frame_master = Frame(self.top, padding = 5)

        label_error = Label(frame_master, text = self.err)
        label_error.pack()

        close = Button(frame_master, text = "OK", width = 8, command= lambda: self.top.destroy() )
        close.pack(side=RIGHT)

        frame_master.pack()

        self.top.resizable(0,0) # stops user from resizing window
        self.top.withdraw()                                                 # un-draw window
        self.top.update_idletasks()                                         # Update "reqwidth" and "reqheight" from geometry manager
        x = (self.top.winfo_screenwidth() - self.top.winfo_reqwidth()) / 2      # Figures out location based on screen and window size
        y = (self.top.winfo_screenheight() - self.top.winfo_reqheight()) / 2    #  "
        self.top.geometry("+%d+%d" % (x, y))                                # places the window at that loction
        self.top.deiconify()                                                # redraws window

class Controller:

    def __init__(self):
        pass

    def get_disk_list(self):
        disk_size_list = []
        if sys.platform == 'darwin':
            # get plist of disks from diskutil
            list_command = ['diskutil', 'list', '-plist']
            try:
                output = subprocess.check_output(list_command)
            except subprocess.CalledProcessError as err:
                raise Exception( "Could not get list of disks. \nError: %s" % err)

            list_plist = plistlib.readPlistFromString(output)

            disk_list =  list_plist["WholeDisks"]
            # parse the plist for sizes. this might be able to be combined with first step eventually.
            for disk in disk_list:
                # maybe someday check for usb drives only. meh.

                size_command = ['diskutil', 'info', '-plist', disk]

                try:
                    output = subprocess.check_output(size_command)
                except subprocess.CalledProcessError as err:
                    raise Exception("Could not get disk information. \nError: %s" % err)

                size_plist = plistlib.readPlistFromString(output)
                size = self.bytes_to_mb( size_plist["TotalSize"] )
                disk_path = "/dev/" + disk

                disk_size_list.append( (disk_path, size, "MB") )

        else:
            disk_size_list = []
            #get list of disks using lsblk
            list_command =  ["lsblk", "-d", "-n", "-o", "NAME,SIZE,TYPE"]
            try:
                output = subprocess.check_output(list_command)
            except subprocess.CalledProcessError as err:
                raise Exception( "Could not get list of disks. Make sure you have 'lsblk' installed. \nError: %s" % err )

            disk_list =  output.splitlines()

            for disk in disk_list:
                disk_info = disk.split()
                disk_path = "/dev/" + disk_info[0]
                if disk_info[2] == "disk":
                    disk_size_list.append( (disk_path, disk_info[1]) )

        return disk_size_list

    def bytes_to_mb(self, bytes):
        mb = bytes / 1048576
        return mb

    def open_disk(self):
        sd = view.disk_choice.get()
        sd = tuple([x for x in sd[1:-1].split("'")]) #super ugly way to make string into tuple
        sd = sd[1]

        global sd_card
        sd_card = disk_functions.Sky3DS_Disk(sd)
        self.fill_rom_table()

    def get_rom_info(self, rom_list):
        rom_data = []
        for rom in rom_list:
            slot = rom[0]
            start = rom[1]
            size = rom[2]
            rom_header = sd_card.ncsd_header(slot)
            rom_info = titles.rom_info(rom_header['product_code'], rom_header['media_id'])
            if rom_info:
                title = rom_info['name']
                firmware = rom_info['firmware']
            else:
                title = "???"
                firmware = "???"
            rom_data.append( [
                slot,
                "%d MB" % int(rom[1] / 1024 / 1024),
                "%d MB" % int(rom[2] / 1024 / 1024),
                rom_header['card_type'],
                rom_header['product_code'],
                title,
                rom_header['save_crypto'].rjust(12),
                ] )
        return rom_data

    def fill_rom_table(self):
        # clear it out first
        rom_table = view.tree_rom_table

        rom_table.delete(*rom_table.get_children());

        rom_data = self.get_rom_info(sd_card.rom_list)

        for rom in rom_data:
            rom_table.insert("", "end", "", values=(rom) )

        total_free_blocks = sum(512*i[1] for i in sd_card.free_blocks)/1024/1024
        view.disk_free_space.set("Free space: %d MB" % total_free_blocks)

        cont_space = 512 * sd_card.free_blocks[0][1]/1024/1024
        view.disk_continuous_space.set("Largest Continuous Space: %d MB" % cont_space)

        view.disk_name.set("Disk: %s " % sd_card.disk_path)

    def write_rom(self):
        try:
            disk = sd_card.disk_path
        except:
            raise Exception("Please open a disk before attempting to write a rom.")

        file_path = tkFileDialog.askopenfilename( initialdir = os.path.expanduser('~/Desktop'), filetypes=[ ("3DS Rom","*.3ds")] )

        if file_path:
            # follow symlink
            file_path = os.path.realpath(file_path)

            # if the template data doesn't exist we might as well just shut it down right here. If the progress window shows up the exception will stop the process so I can't destrot that toplevel.
            if not self.get_rom_template_data(file_path):
                tkMessageBox.showinfo("Missing Template Data", "Template entry not found.")
                return None

            # get rom size and calculate block count
            rom_size = os.path.getsize(file_path)

            self.message = "Writing %s" % os.path.basename(file_path)
            progress_bar = progress_bar_window("Writing Rom", self.message, mode = "determinate", maximum = rom_size, task = "write")

            root.wait_visibility(progress_bar.window)

            sd_card.write_rom(file_path, progress=progress_bar)

            controller.fill_rom_table()

        else:
            return

    def get_rom_template_data(self, rom_path):

        # open rom file
        romfp = open(rom_path, "rb")

        # get card specific data from template.txt
        serial = gamecard.ncsd_serial(romfp)
        sha1 = gamecard.ncch_sha1sum(romfp)
        template_data = titles.get_template(serial, sha1)
        if not template_data:
            return None
        else:
            return template_data

    def delete_rom(self):
        try:
            rom_list = sd_card.rom_list
        except:
            raise Exception("Please open a disk before attempting to delete a rom.")

        index = view.tree_rom_table.focus()
        if index:
            item = view.tree_rom_table.item(index)

            slot = item['values'][0]

            delete_list = []
            delete_list.append(rom_list[slot])

            rom_info = controller.get_rom_info(delete_list)[0]

            title = rom_info[5]
            slot = rom_info[0] # Instead of relying on the treeview and sd_card to be identical I just pull all of the info out of the sd_card

        else:
            raise Exception("Please select a rom to delete")

        message = "Delete rom %s, %s?" % (slot, title)

        confirm = tkMessageBox.askokcancel("Confirm Delete", message)

        if confirm == True:
            message = "Deleting %s." % title
            task_text = "delete"

            progress_bar = progress_bar_window("Deleting Rom", message, mode = "indeterminate", maximum = 100, task = task_text)

            root.wait_visibility(progress_bar.progress)

            progress_bar.start_indeterminate()

            sd_card.delete_rom(slot)

            progress_bar.progress_complete()

            controller.fill_rom_table()

        else:
            return

    def backup_rom(self):
        try:
            rom_list = sd_card.rom_list
        except:
            raise Exception("Please open a disk before attempting to dump a rom.")

        index = view.tree_rom_table.focus()
        if index:
            item = view.tree_rom_table.item(index)

            tree_slot = item['values'][0]

            delete_list = []
            delete_list.append(rom_list[tree_slot])

            rom_info = controller.get_rom_info(delete_list)[0]

            title = rom_info[5]
            rom_code = rom_info[4]
            index = rom_info[0] # Instead of relying on the treeview and sd_card to be identical I just pull all of the info out of the sd_card
            slot = rom_list[index][0]
            rom_size = rom_list[index][2]

        else:
            raise Exception("Please select a rom to dump.")

        destination_folder = tkFileDialog.askdirectory( initialdir = os.path.expanduser('~/Desktop'), mustexist = True)

        message = "Dump rom %s, %s to %s?" % (slot, title, destination_folder)

        if destination_folder:
            confirm = tkMessageBox.askokcancel("Confirm Rom Dump", message)
        else:
            return

        if confirm == True:
            destination_file = destination_folder + "/" + rom_code + ".3ds"

            message = "Dumping %s to %s" % (rom_code, destination_folder)
            task_text = "%s backup" % title

            progress_bar = progress_bar_window("Dumping Rom", message, mode = "determinate", maximum = rom_size, task = task_text)
            root.wait_visibility(progress_bar.window)

            sd_card.dump_rom( slot, destination_file, progress=progress_bar)
        else:
            return

    def backup_save(self):
        try:
            rom_list = sd_card.rom_list
        except:
            raise Exception("Please open a disk before attempting to backup a save.")

        index = view.tree_rom_table.focus()
        if index:
            item = view.tree_rom_table.item(index)

            tree_slot = item['values'][0]

            delete_list = []
            delete_list.append(rom_list[tree_slot])

            rom_info = controller.get_rom_info(delete_list)[0]

            title = rom_info[5]
            rom_code = rom_info[4]
            index = rom_info[0] # Instead of relying on the treeview and sd_card to be identical I check the rom_list
            slot = rom_list[index][0]

        else:
            raise Exception("Please select a rom to backup a save")

        destination_folder = tkFileDialog.askdirectory( initialdir = os.path.expanduser('~/Desktop'), mustexist = True)

        message = "Backup save for rom %s, %s to %s?" % (slot, title, destination_folder)

        if destination_folder:
            confirm = tkMessageBox.askokcancel("Confirm Save Backup", message)
        else:
            return

        if confirm == True:
            destination_file = destination_folder + "/" + rom_code + ".sav"

            message = "Dumping save from %s to %s" % (rom_code, destination_folder)
            task_text = "%s save backup" % title

            progress_bar = progress_bar_window("Dumping Save", message, mode = "indeterminate", maximum = 100, task = task_text)

            root.wait_visibility(progress_bar.progress)

            progress_bar.start_indeterminate()

            sd_card.dump_savegame( slot, destination_file )

            progress_bar.progress_complete()

        else:
            return


    def write_save(self):
        try:
            disk = sd_card.disk_path
        except:
            raise Exception("Please open a disk before attempting to write a save.")

        file_path = tkFileDialog.askopenfilename( initialdir = os.path.expanduser('~/Desktop'), filetypes=[ ("3DS Save","*.sav")] )

        if file_path:
            # follow symlink
            file_path = os.path.realpath(file_path)

            self.message = "Writing %s" % os.path.basename(file_path)

            progress_bar = progress_bar_window("Writing Save", self.message, mode = "indeterminate", maximum = 100, task = "write save")

            root.wait_visibility(progress_bar.progress)

            progress_bar.start_indeterminate()

            sd_card.write_savegame(file_path)

            progress_bar.progress_complete()

            controller.fill_rom_table()

        else:
            return


    def check_for_template(self):
        root.update_idletasks() #since this is run during view __init__ before mainloop finishes a loop we have to update tk() so it responds properly to input events.

        data_dir = user_data_dir('sky3ds', 'Aperture Laboratories')
        template_txt = os.path.join(data_dir, 'template.txt')

        if not os.path.isfile(template_txt):
            tkMessageBox.showinfo("Template.txt not found.", "Template.txt not found, please select a Sky3ds template file")
            update_template()
        else:
            pass


class progress_bar_window:
    def __init__(self, title, message, mode, maximum, task):
        self.message = message
        self.mode = mode
        self.maximum = maximum
        self.task = task

        self.window = Toplevel(background = "#e2e2e2")
        self.window.wm_attributes("-topmost", 1)
        self.window.resizable(0,0) # stops user from resizing window
        self.window.title(title)
        self.window.grab_set()

        self.create_widgets()

        self.window.protocol('WM_DELETE_WINDOW', self.on_progress_bar_close )

        self.bar_position = 0

        self.start_indeterminate()

    def create_widgets(self):

        frame_master = Frame(self.window, padding = 5)

        label_message = Label(frame_master, text = self.message)
        label_message.pack()

        self.progress = ttk.Progressbar(frame_master, mode = self.mode, orient="horizontal", length = 300)
        self.progress.pack()
        self.progress['maximum'] = self.maximum

        frame_master.pack()

    def start_indeterminate(self):
        if self.mode == "indeterminate":
            self.progress.start(50)
            root.update_idletasks()
        else:
            pass

    def update(self, total_written):
        amount = total_written - self.bar_position

        self.bar_position = self.bar_position + amount

        if total_written == self.maximum:
            self.progress_complete()
        else:
            self.progress["value"] = total_written
            self.progress.update_idletasks()

    def progress_complete(self):
        capital_task = self.task.capitalize()
        message = "%s successful." % capital_task
        self.window.grab_release()
        self.window.wm_attributes("-topmost", 0)
        done = tkMessageBox.showinfo("Complete", message)
        self.window.destroy()

    def on_progress_bar_close(self):
        message = "Please wait for %s to complete." % self.task
        tkMessageBox.showinfo("Cannot Close", message)


class update_template:
    def __init__(self):
        data_dir = user_data_dir('sky3ds', 'Aperture Laboratories')
        template_txt = os.path.join(data_dir, 'template.txt')

        file_name = tkFileDialog.askopenfile( initialdir = os.path.expanduser('~/Desktop'), filetypes=[ ("Text files","*.txt")] )

        if file_name:
            try:
                new_template = file_name.read()
                write_template = open(template_txt, 'w')
                write_template.write(new_template)

                file_name.close()
                write_template.close()

                tkMessageBox.showinfo("Template Updated", "Template.txt updated successfully")

            except:
                raise Exception("Template.txt could not be updated")

            try:
                titles.convert_template_to_json()
            except:
                raise Exception("Template.txt could not converted to JSON. Please verify that your template.txt is not corrupt.")
        else:
            return

class update_titles:
    def __init__(self):
        try:
            message = "Update Titles database"

            progress_bar = progress_bar_window("Updating Titles", message, mode="indeterminate", maximum = 100, task = "Titles update")

            root.wait_visibility(progress_bar.progress)

            progress_bar.start_indeterminate()

            titles.update_title_db()

            progress_bar.progress_complete()

        except:
            raise Exception("Titles database could not be updated.")


class format_disk:
    def __init__(self):
        self.window = Toplevel()
        self.window.wm_attributes("-topmost", 1)
        self.window.title("Format Disk")
        self.window.grab_set()

        self.create_widgets()

        self.modify_window()

        self.update_disk_optionmenu()

        self.window.protocol('WM_DELETE_WINDOW', self.on_format_window_close )

    def create_widgets(self):
        frame_master = Frame(self.window, padding = 5)

        frame_top_row = Frame(frame_master)

        label_choose = Label(frame_top_row, text = "Choose Disk:")
        label_choose.pack(side=LEFT)

        self.disk_choice = StringVar()
        self.menu_choose_disk = OptionMenu(frame_top_row, self.disk_choice, '')
        self.menu_choose_disk.pack(side = LEFT, fill=BOTH, expand=1)
        self.menu_choose_disk.configure(width = 30)

        button_refresh_list = Button(frame_top_row, text = "Refresh List", command = self.update_disk_optionmenu )
        button_refresh_list.pack(side=LEFT)

        frame_top_row.pack()

        frame_bottom_row = Frame(frame_master)

        button_format_disk = Button(frame_bottom_row, text = "Format Disk", command = self.format_confirm )
        button_format_disk.pack(side=LEFT)

        frame_bottom_row.pack()

        frame_master.pack()

    def modify_window(self):
        self.window.resizable(0,0) # stops user from resizing window
        self.window.withdraw()                                                 # un-draw window
        self.window.update_idletasks()                                         # Update "reqwidth" and "reqheight" from geometry manager
        x = (self.window.winfo_screenwidth() - self.window.winfo_reqwidth()) / 2      # Figures out location based on screen and window size
        y = (self.window.winfo_screenheight() - self.window.winfo_reqheight()) / 2    #  "
        self.window.geometry("+%d+%d" % (x, y))                                # places the window at that loction
        self.window.deiconify()                                                # redraws window

    def update_disk_optionmenu(self):
        disk_list = controller.get_disk_list()
        menu = self.menu_choose_disk['menu']
        # clear all options
        menu.delete(0, 'end')
        # add new
        if not disk_list:
            raise Exception("No Acceptable disks found")
        else:
            for disk in disk_list:
                menu.add_command(label=disk, command=_setit(self.disk_choice,disk))
                self.disk_choice.set(disk)

    def format_confirm(self):
        sd = self.disk_choice.get()
        sd = tuple([x for x in sd[1:-1].split("'")]) #super ugly way to make string into tuple
        sd = sd[1]

        message = "Formatting %s will permanently erase all data and partitions on %s. \nPlease confirm that you have chosen the correct disk to format. \n \nFormatting can take several minutes to complete, please be patient." % (sd, sd)

        self.window.grab_release()
        self.window.wm_attributes("-topmost", 0)
        confirm = tkMessageBox.askokcancel("Confirm Format", message)
        self.window.wm_attributes("-topmost", 1)
        self.window.grab_set()

        if confirm == True:
            self.format_disk(sd)
        else:
            return

    def format_disk(self, sd):
        #first unmount all volums from disk
        if sys.platform == 'darwin':
            list_command = ["diskutil", "unmountDisk", sd]
            try:
                output = subprocess.check_output(list_command)
            except subprocess.CalledProcessError as err:
                raise Exception( "Could not get unmount partitions on %s. \nError: %s" % (sd, err) )
        else:
            # linux really just doesn't care if a volume is mounted, it'll let us format it anyway
            pass

        # pass it along to disk class
        disk_to_format = disk_functions.Sky3DS_Disk(sd)

        message = "Formatting %s" % sd

        progress_bar = progress_bar_window("Formatting Disk", message, mode="indeterminate", maximum = 100, task = "format")

        root.wait_visibility(progress_bar.progress)

        progress_bar.start_indeterminate()

        disk_to_format.format()

        progress_bar.progress_complete()

    def on_format_window_close(self):
        view.update_disk_optionmenu()
        self.window.destroy()

class help_window:
    def __init__(self):
        self.top = Toplevel()
        #root.grab_set() # keeps other windows from being active over this one
        self.top.wm_attributes('-topmost', 1) # keeps this window on top of other
        self.top.title("Diskwriter Help")

        self.create_widgets()

        self.modify_window()

    def create_widgets(self):

        help_file = self.open_help_file()

        frame_help_text = Frame(self.top)

        message_help_file = Message( frame_help_text, text = help_file)

        message_help_file.pack()


        button_close = Button(frame_help_text, text = "Close", command = self.close_window )
        button_close.pack()

        frame_help_text.pack(padx=5, pady=5)

    def modify_window(self):
        self.top.withdraw()                                                 # un-draw window
        self.top.update_idletasks()                                         # Update "reqwidth" and "reqheight" from geometry manager
        x = (self.top.winfo_screenwidth() + view.root.winfo_reqwidth()) / 2 + 8      # Figures out location based on screen and window size
        y = (self.top.winfo_screenheight() - self.top.winfo_reqheight()) / 2    # "
        self.top.geometry("+%d+%d" % (x, y))                                # places the window at that loction
        self.top.deiconify()                                                # redraws window

    def close_window(self):
        self.top.destroy()


    def open_help_file(self):
        help_file = open('help_file.txt', 'r')
        return help_file.read()





if __name__ == '__main__':
    controller = Controller()
    view = View(root)
    root.mainloop()