NEW GUI sample app
--HG-- branch : sandbox
This commit is contained in:
@@ -8,8 +8,8 @@
|
|||||||
import Tkinter as Tk
|
import Tkinter as Tk
|
||||||
import logging
|
import logging
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from PIL import Image
|
import Image
|
||||||
from PIL import ImageTk
|
import ImageTk
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
|
|
||||||
|
|||||||
993
src/misc/DirectoryPruner1.py
Normal file
993
src/misc/DirectoryPruner1.py
Normal file
@@ -0,0 +1,993 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
"""Module providing GUI capability to prune any directory.
|
||||||
|
|
||||||
|
The code presented in this module is for the purposes of: (1) ascertaining
|
||||||
|
the space taken up by a directory, its files, its sub-directories, and its
|
||||||
|
sub-files; (2) allowing for the removal of the sub-files, sub-directories,
|
||||||
|
files, and directory found in the first purpose; (3) giving the user a GUI
|
||||||
|
to accomplish said purposes in a convenient way that is easily accessible."""
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower@gmail.com>'
|
||||||
|
__date__ = '15 February 2011'
|
||||||
|
__version__ = '$Revision: 298 $'
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Import several GUI libraries.
|
||||||
|
import tkinter
|
||||||
|
import tkinter.ttk
|
||||||
|
import tkinter.Filedialog
|
||||||
|
import tkinter.Messagebox
|
||||||
|
|
||||||
|
# Import other needed modules.
|
||||||
|
import zlib
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import math
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
ICON = b'eJxjYGAEQgEBBiApwZDBzMAgxsDAoAHEQCEGBQaIOAwkQDE2UOSkiUM\
|
||||||
|
Gp/rlyd740Ugzf8/uXROxAaA4VvVAqcfYAFCcoHqge4hR/+btWwgCqoez8aj//fs\
|
||||||
|
XWiAARfCrhyCg+XA2HvV/YACoHs4mRj0ywKWe1PD//p+B4QMOmqGeMAYAAY/2nw=='
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
class GUISizeTree(tkinter.ttk.Frame):
|
||||||
|
|
||||||
|
"Widget for examining size of directory with optional deletion."
|
||||||
|
|
||||||
|
WARN = True # Should warnings be made for permanent operations?
|
||||||
|
MENU = True # Should the (destructive) context menu be enabled?
|
||||||
|
|
||||||
|
# Give names to columns.
|
||||||
|
CLMS = 'total_size', 'file_size', 'path'
|
||||||
|
TREE = '#0'
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Allow direct execution of GUISizeTree widget.
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def main(cls):
|
||||||
|
"Create an application containing a single GUISizeTree widget."
|
||||||
|
tkinter.NoDefaultRoot()
|
||||||
|
root = cls.create_application_root()
|
||||||
|
cls.attach_window_icon(root, ICON)
|
||||||
|
view = cls.setup_class_instance(root)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_application_root():
|
||||||
|
"Create and configure the main application window."
|
||||||
|
root = tkinter.Tk()
|
||||||
|
root.minsize(430, 215)
|
||||||
|
root.title('Directory Pruner')
|
||||||
|
root.option_add('*tearOff', tkinter.FALSE)
|
||||||
|
return root
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def attach_window_icon(root, icon):
|
||||||
|
"Generate and use the icon in the window's corner."
|
||||||
|
with open('tree.ico', 'wb') as file:
|
||||||
|
file.write(zlib.decompress(base64.b64decode(ICON)))
|
||||||
|
root.iconbitmap('tree.ico')
|
||||||
|
os.remove('tree.ico')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_class_instance(cls, root):
|
||||||
|
"Build GUISizeTree instance that expects resizing."
|
||||||
|
instance = cls(root)
|
||||||
|
instance.grid(row=0, column=0, sticky=tkinter.NSEW)
|
||||||
|
root.grid_rowconfigure(0, weight=1)
|
||||||
|
root.grid_columnconfigure(0, weight=1)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Initialize the GUISizeTree object.
|
||||||
|
|
||||||
|
def __init__(self, master=None, **kw):
|
||||||
|
"Initialize the GUISizeTree instance and configure for operation."
|
||||||
|
super().__init__(master, **kw)
|
||||||
|
# Initialize and configure this frame widget.
|
||||||
|
self.capture_root()
|
||||||
|
self.create_widgets()
|
||||||
|
self.create_supports()
|
||||||
|
self.create_bindings()
|
||||||
|
self.configure_grid()
|
||||||
|
self.configure_tree()
|
||||||
|
self.configure_menu()
|
||||||
|
# Set focus to path entry.
|
||||||
|
self.__path.focus_set()
|
||||||
|
|
||||||
|
def capture_root(self):
|
||||||
|
"Capture the root (Tk instance) of this application."
|
||||||
|
widget = self.master
|
||||||
|
while not isinstance(widget, tkinter.Tk):
|
||||||
|
widget = widget.master
|
||||||
|
self.__tk = widget
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
"Create all the widgets that will be placed in this frame."
|
||||||
|
self.__label = tkinter.ttk.Button(self, text='Path:',
|
||||||
|
command=self.choose)
|
||||||
|
self.__path = tkinter.ttk.Entry(self, cursor='xterm')
|
||||||
|
self.__run = tkinter.ttk.Button(self, text='Search',
|
||||||
|
command=self.search)
|
||||||
|
self.__cancel = tkinter.ttk.Button(self, text='Cancel',
|
||||||
|
command=self.stop_search)
|
||||||
|
self.__progress = tkinter.ttk.Progressbar(self,
|
||||||
|
orient=tkinter.HORIZONTAL)
|
||||||
|
self.__tree = tkinter.ttk.Treeview(self, columns=self.CLMS,
|
||||||
|
selectmode=tkinter.BROWSE)
|
||||||
|
self.__scroll_1 = tkinter.ttk.Scrollbar(self, orient=tkinter.VERTICAL,
|
||||||
|
command=self.__tree.yview)
|
||||||
|
self.__scroll_2 = tkinter.ttk.Scrollbar(self, orient=tkinter.HORIZONTAL,
|
||||||
|
command=self.__tree.xview)
|
||||||
|
self.__grip = tkinter.ttk.Sizegrip(self)
|
||||||
|
|
||||||
|
def create_supports(self):
|
||||||
|
"Create all GUI elements not placed directly in this frame."
|
||||||
|
self.__menu = tkinter.Menu(self)
|
||||||
|
self.create_directory_browser()
|
||||||
|
self.create_error_message()
|
||||||
|
self.create_warning_message()
|
||||||
|
|
||||||
|
def create_directory_browser(self):
|
||||||
|
"Find root of file system and create directory browser."
|
||||||
|
head, tail = os.getcwd(), True
|
||||||
|
while tail:
|
||||||
|
head, tail = os.path.split(head)
|
||||||
|
self.__dialog = tkinter.filedialog.Directory(self, initialdir=head)
|
||||||
|
|
||||||
|
def create_error_message(self):
|
||||||
|
"Create error message when trying to search bad path."
|
||||||
|
options = {'title': 'Path Error',
|
||||||
|
'icon': tkinter.messagebox.ERROR,
|
||||||
|
'type': tkinter.messagebox.OK,
|
||||||
|
'message': 'Directory does not exist.'}
|
||||||
|
self.__error = tkinter.messagebox.Message(self, **options)
|
||||||
|
|
||||||
|
def create_warning_message(self):
|
||||||
|
"Create warning message for permanent operations."
|
||||||
|
options = {'title': 'Important Warning',
|
||||||
|
'icon': tkinter.messagebox.QUESTION,
|
||||||
|
'type': tkinter.messagebox.YESNO,
|
||||||
|
'message': '''\
|
||||||
|
You cannot undo these operations.
|
||||||
|
Are you sure you want to do this?'''}
|
||||||
|
self.__warn = tkinter.messagebox.Message(self, **options)
|
||||||
|
|
||||||
|
def create_bindings(self):
|
||||||
|
"Bind the widgets to any events they will need to handle."
|
||||||
|
self.__label.bind('<Return>', self.choose)
|
||||||
|
self.__path.bind('<Control-Key-a>', self.select_all)
|
||||||
|
self.__path.bind('<Control-Key-/>', lambda event: 'break')
|
||||||
|
self.__path.bind('<Return>', self.search)
|
||||||
|
self.__run.bind('<Return>', self.search)
|
||||||
|
self.__cancel.bind('<Return>', self.stop_search)
|
||||||
|
self.bind_right_click(self.__tree, self.open_menu)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def select_all(event):
|
||||||
|
"Select all of the contents in this Entry widget."
|
||||||
|
event.widget.selection_range(0, tkinter.END)
|
||||||
|
return 'break'
|
||||||
|
|
||||||
|
def bind_right_click(self, widget, action):
|
||||||
|
"Bind action to widget while considering Apple computers."
|
||||||
|
if self.__tk.tk.call('tk', 'windowingsystem') == 'aqua':
|
||||||
|
widget.bind('<2>', action)
|
||||||
|
widget.bind('<Control-1>', action)
|
||||||
|
else:
|
||||||
|
widget.bind('<3>', action)
|
||||||
|
|
||||||
|
def configure_grid(self):
|
||||||
|
"Place all widgets on the grid in their respective locations."
|
||||||
|
self.__label.grid(row=0, column=0)
|
||||||
|
self.__path.grid(row=0, column=1, sticky=tkinter.EW)
|
||||||
|
self.__run.grid(row=0, column=2, columnspan=2)
|
||||||
|
self.__run.grid_remove()
|
||||||
|
self.__cancel.grid(row=0, column=2, columnspan=2)
|
||||||
|
self.__cancel.grid_remove()
|
||||||
|
self.__run.grid()
|
||||||
|
self.__progress.grid(row=1, column=0, columnspan=4, sticky=tkinter.EW)
|
||||||
|
self.__tree.grid(row=2, column=0, columnspan=3, sticky=tkinter.NSEW)
|
||||||
|
self.__scroll_1.grid(row=2, column=3, sticky=tkinter.NS)
|
||||||
|
self.__scroll_2.grid(row=3, column=0, columnspan=3, sticky=tkinter.EW)
|
||||||
|
self.__grip.grid(row=3, column=3, sticky=tkinter.SE)
|
||||||
|
# Configure the grid to automatically resize internal widgets.
|
||||||
|
self.grid_rowconfigure(2, weight=1)
|
||||||
|
self.grid_columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
def configure_tree(self):
|
||||||
|
"Configure the Treeview widget."
|
||||||
|
# Setup the headings.
|
||||||
|
self.__tree.heading(self.TREE, text=' Name', anchor=tkinter.W,
|
||||||
|
command=self.sort_name)
|
||||||
|
self.__tree.heading(self.CLMS[0], text=' Total Size', anchor=tkinter.W,
|
||||||
|
command=self.sort_total_size)
|
||||||
|
self.__tree.heading(self.CLMS[1], text=' File Size', anchor=tkinter.W,
|
||||||
|
command=self.sort_file_size)
|
||||||
|
self.__tree.heading(self.CLMS[2], text=' Path', anchor=tkinter.W,
|
||||||
|
command=self.sort_path)
|
||||||
|
# Setup the columns.
|
||||||
|
self.__tree.column(self.TREE, minwidth=100, width=200)
|
||||||
|
self.__tree.column(self.CLMS[0], minwidth=100, width=200)
|
||||||
|
self.__tree.column(self.CLMS[1], minwidth=100, width=200)
|
||||||
|
self.__tree.column(self.CLMS[2], minwidth=100, width=200)
|
||||||
|
# Connect the Scrollbars.
|
||||||
|
self.__tree.configure(yscrollcommand=self.__scroll_1.set)
|
||||||
|
self.__tree.configure(xscrollcommand=self.__scroll_2.set)
|
||||||
|
|
||||||
|
def configure_menu(self):
|
||||||
|
"Configure the (context) Menu widget."
|
||||||
|
# Shortcut for narrowing the search.
|
||||||
|
self.__menu.add_command(label='Search Directory',
|
||||||
|
command=self.search_dir)
|
||||||
|
self.__menu.add_separator()
|
||||||
|
# Operations committed on directory.
|
||||||
|
self.__menu.add_command(label='Remove Directory', command=self.rm_dir)
|
||||||
|
self.__menu.add_command(label='Remove Files', command=self.rm_files)
|
||||||
|
self.__menu.add_separator()
|
||||||
|
# Operations that recurse on sub-directories.
|
||||||
|
self.__menu.add_command(label='Remove Sub-directories',
|
||||||
|
command=self.rm_subdirs)
|
||||||
|
self.__menu.add_command(label='Remove Sub-files',
|
||||||
|
command=self.rm_subfiles)
|
||||||
|
# Only add "Open Directory" command on Windows.
|
||||||
|
if hasattr(os, 'startfile'):
|
||||||
|
self.__menu.add_separator()
|
||||||
|
self.__menu.add_command(label='Open Directory',
|
||||||
|
command=self.open_dir)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# This property is used to control access to operations.
|
||||||
|
|
||||||
|
def __get_operations_enabled(self):
|
||||||
|
"Return if run button is in normal state."
|
||||||
|
return self.__run['state'].string == tkinter.NORMAL
|
||||||
|
|
||||||
|
def __set_operations_enabled(self, value):
|
||||||
|
"Enable or disable run button's state according to value."
|
||||||
|
self.__run['state'] = tkinter.NORMAL if value else tkinter.DISABLED
|
||||||
|
|
||||||
|
operations_enabled = property(__get_operations_enabled,
|
||||||
|
__set_operations_enabled,
|
||||||
|
doc="Flag controlling certain operations")
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Handle path browsing and searching actions.
|
||||||
|
|
||||||
|
def choose(self, event=None):
|
||||||
|
"Show directory browser and set path as needed."
|
||||||
|
path = self.__dialog.show()
|
||||||
|
if path:
|
||||||
|
# Entry is cleared before absolute path is added.
|
||||||
|
self.__path.delete(0, tkinter.END)
|
||||||
|
self.__path.insert(0, os.path.abspath(path))
|
||||||
|
|
||||||
|
def search(self, event=None):
|
||||||
|
"Search the path and display the size of the directory."
|
||||||
|
if self.operations_enabled:
|
||||||
|
self.operations_enabled = False
|
||||||
|
# Get absolute path and check existence.
|
||||||
|
path = os.path.abspath(self.__path.get())
|
||||||
|
if os.path.isdir(path):
|
||||||
|
# Enable operations after finishing search.
|
||||||
|
self.__search(path)
|
||||||
|
self.operations_enabled = True
|
||||||
|
else:
|
||||||
|
self.shake()
|
||||||
|
|
||||||
|
def __search(self, path):
|
||||||
|
"Execute the search procedure and display in Treeview."
|
||||||
|
self.__run.grid_remove()
|
||||||
|
self.__cancel.grid()
|
||||||
|
children = self.start_search()
|
||||||
|
try:
|
||||||
|
tree = SizeTree(self.update_search, path)
|
||||||
|
except StopIteration:
|
||||||
|
self.handle_stop_search(children)
|
||||||
|
else:
|
||||||
|
self.finish_search(children, tree)
|
||||||
|
self.__cancel.grid_remove()
|
||||||
|
self.__run.grid()
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Execute various phases of a search.
|
||||||
|
|
||||||
|
def start_search(self):
|
||||||
|
"Edit the GUI in preparation for executing a search."
|
||||||
|
self.__stop_search = False
|
||||||
|
children = Apply(TreeviewNode(self.__tree).children)
|
||||||
|
children.detach()
|
||||||
|
self.__progress.configure(mode='indeterminate', maximum=100)
|
||||||
|
self.__progress.start()
|
||||||
|
return children
|
||||||
|
|
||||||
|
def update_search(self):
|
||||||
|
"Check if search has been stopped and update the GUI."
|
||||||
|
self.validate_search()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def validate_search(self):
|
||||||
|
"Check that the current search action is valid."
|
||||||
|
if self.__stop_search:
|
||||||
|
self.__stop_search = False
|
||||||
|
raise StopIteration('Search has been canceled!')
|
||||||
|
|
||||||
|
def stop_search(self, event=None):
|
||||||
|
"Cancel a search by setting its stop flag."
|
||||||
|
self.__stop_search = True
|
||||||
|
|
||||||
|
def handle_stop_search(self, children):
|
||||||
|
"Reset the Treeview and Progressbar on premature termination."
|
||||||
|
children.reattach()
|
||||||
|
self.__progress.stop()
|
||||||
|
self.__progress['mode'] = 'determinate'
|
||||||
|
|
||||||
|
def finish_search(self, children, tree):
|
||||||
|
"Delete old children, update Progressbar, and update Treeview."
|
||||||
|
children.delete()
|
||||||
|
self.__progress.stop()
|
||||||
|
self.__progress.configure(mode='determinate',
|
||||||
|
maximum=tree.total_nodes+1)
|
||||||
|
node = TreeviewNode(self.__tree).append(tree.name)
|
||||||
|
try:
|
||||||
|
self.build_tree(node, tree)
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Handle Treeview column sorting events initiated by user.
|
||||||
|
|
||||||
|
def sort_name(self):
|
||||||
|
"Sort children of selected node by name."
|
||||||
|
TreeviewNode.current(self.__tree).sort_name()
|
||||||
|
|
||||||
|
def sort_total_size(self):
|
||||||
|
"Sort children of selected node by total size."
|
||||||
|
TreeviewNode.current(self.__tree).sort_total_size()
|
||||||
|
|
||||||
|
def sort_file_size(self):
|
||||||
|
"Sort children of selected node by file size."
|
||||||
|
TreeviewNode.current(self.__tree).sort_file_size()
|
||||||
|
|
||||||
|
def sort_path(self):
|
||||||
|
"Sort children of selected node by path."
|
||||||
|
TreeviewNode.current(self.__tree).sort_path()
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Handle right-click events on the Treeview widget.
|
||||||
|
|
||||||
|
def open_menu(self, event):
|
||||||
|
"Select Treeview row and show context menu if allowed."
|
||||||
|
item = event.widget.identify_row(event.y)
|
||||||
|
if item:
|
||||||
|
event.widget.selection_set(item)
|
||||||
|
if self.menu_allowed:
|
||||||
|
self.__menu.post(event.x_root, event.y_root)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def menu_allowed(self):
|
||||||
|
"Check if menu is enabled along with operations."
|
||||||
|
return self.MENU and self.operations_enabled
|
||||||
|
|
||||||
|
def search_dir(self):
|
||||||
|
"Search the path of the currently selected row."
|
||||||
|
path = TreeviewNode.current(self.__tree).path
|
||||||
|
self.__path.delete(0, tkinter.END)
|
||||||
|
self.__path.insert(0, path)
|
||||||
|
self.search()
|
||||||
|
|
||||||
|
def rm_dir(self):
|
||||||
|
"Remove the currently selected directory."
|
||||||
|
if self.commit_permanent_operation:
|
||||||
|
self.do_remove_directory()
|
||||||
|
|
||||||
|
def rm_files(self):
|
||||||
|
"Remove the files in the currently selected directory."
|
||||||
|
if self.commit_permanent_operation:
|
||||||
|
self.do_remove_files()
|
||||||
|
|
||||||
|
def rm_subdirs(self):
|
||||||
|
"Remove the sub-directories of the currently selected directory."
|
||||||
|
if self.commit_permanent_operation:
|
||||||
|
self.do_remove_subdirectories()
|
||||||
|
|
||||||
|
def rm_subfiles(self):
|
||||||
|
"Remove the sub-files of the currently selected directory."
|
||||||
|
if self.commit_permanent_operation:
|
||||||
|
self.do_remove_subfiles()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def commit_permanent_operation(self):
|
||||||
|
"Check if warning should be issued before committing operation."
|
||||||
|
return not self.WARN or self.__warn.show() == tkinter.messagebox.YES
|
||||||
|
|
||||||
|
def open_dir(self):
|
||||||
|
"Open up the current directory (only available on Windows)."
|
||||||
|
os.startfile(TreeviewNode.current(self.__tree).path)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Execute actions requested by context menu.
|
||||||
|
|
||||||
|
def do_remove_directory(self):
|
||||||
|
"Remove a directory and all of its sub-directories."
|
||||||
|
self.begin_rm()
|
||||||
|
# Get the current Treeview node and delete it.
|
||||||
|
node = TreeviewNode.current(self.__tree)
|
||||||
|
directory_size, path = node.total_size, node.path
|
||||||
|
position, parent = node.position, node.delete(True)
|
||||||
|
# Delete the entire directory at path.
|
||||||
|
self.__rm_dir(self.update, path, True, True)
|
||||||
|
if os.path.isdir(path):
|
||||||
|
# Add the directory back to the Treeview.
|
||||||
|
tree = SizeTree(self.update, path)
|
||||||
|
self.begin_rm_update(tree.total_nodes + 1)
|
||||||
|
# Rebuild the Treeview under the parent.
|
||||||
|
node = parent.insert(position, tree.name)
|
||||||
|
self.build_tree(node, tree)
|
||||||
|
# New directory size.
|
||||||
|
total_size = tree.total_size
|
||||||
|
else:
|
||||||
|
self.begin_rm_update()
|
||||||
|
# New directory size.
|
||||||
|
total_size = 0
|
||||||
|
# If the size has changed, update parent nodes.
|
||||||
|
if directory_size != total_size:
|
||||||
|
diff = total_size - directory_size
|
||||||
|
self.update_parents(parent, diff)
|
||||||
|
self.end_rm()
|
||||||
|
|
||||||
|
def do_remove_files(self):
|
||||||
|
"Remove all of the files in the selected directory."
|
||||||
|
# Delete files in the directory and get its new size.
|
||||||
|
node = TreeviewNode.current(self.__tree)
|
||||||
|
total_size = self.__rm_files(node.path)
|
||||||
|
# Update current and parent nodes if the size changed.
|
||||||
|
if node.file_size != total_size:
|
||||||
|
diff = total_size - node.file_size
|
||||||
|
node.file_size = total_size
|
||||||
|
node.total_size += diff
|
||||||
|
self.update_parents(node.parent, diff)
|
||||||
|
|
||||||
|
def do_remove_subdirectories(self):
|
||||||
|
"Remove all subdirectories in the directory."
|
||||||
|
self.begin_rm()
|
||||||
|
# Remove all the children nodes in Viewtree.
|
||||||
|
node = TreeviewNode.current(self.__tree)
|
||||||
|
for child in node.children:
|
||||||
|
child.delete()
|
||||||
|
# Delete all of the subdirectories and their files.
|
||||||
|
self.__rm_dir(self.update, node.path, True)
|
||||||
|
# Find out what subdirectories could not be deteled.
|
||||||
|
tree = SizeTree(self.update, node.path)
|
||||||
|
self.begin_rm_update(tree.total_nodes)
|
||||||
|
if tree.total_nodes:
|
||||||
|
# Rebuild the Viewtree as needed.
|
||||||
|
self.build_tree(node, tree, False)
|
||||||
|
# Fix node and prepare to update parents.
|
||||||
|
diff = node.total_size - tree.total_size
|
||||||
|
node.total_size = tree.total_size
|
||||||
|
else:
|
||||||
|
# Fix node and prepare to update parents.
|
||||||
|
diff = node.file_size - node.total_size
|
||||||
|
node.total_size = node.file_size
|
||||||
|
# Update parents with new size.
|
||||||
|
self.update_parents(node.parent, diff)
|
||||||
|
self.end_rm()
|
||||||
|
|
||||||
|
def do_remove_subfiles(self):
|
||||||
|
"Remove all subfiles while keeping subdirectories in place."
|
||||||
|
self.begin_rm()
|
||||||
|
# Delete all subfiles from current directory.
|
||||||
|
node = TreeviewNode.current(self.__tree)
|
||||||
|
self.__rm_dir(self.update, node.path)
|
||||||
|
# Build a new SizeTree to find the result.
|
||||||
|
tree = SizeTree(self.update, node.path)
|
||||||
|
self.begin_rm_update(tree.total_nodes)
|
||||||
|
# Record the difference and patch the Viewtree.
|
||||||
|
diff = tree.total_size - node.total_size
|
||||||
|
self.patch_tree(node, tree)
|
||||||
|
# Fix all parent nodes with the correct size.
|
||||||
|
self.update_parents(node.parent, diff)
|
||||||
|
self.end_rm()
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Help update Progressbar in removal process.
|
||||||
|
|
||||||
|
def begin_rm(self):
|
||||||
|
"Start a long-running removal operation."
|
||||||
|
self.operations_enabled = False
|
||||||
|
self.__progress.configure(mode='indeterminate', maximum=100)
|
||||||
|
self.__progress.start()
|
||||||
|
|
||||||
|
def begin_rm_update(self, nodes=0):
|
||||||
|
"Move to determinate mode of updating the Viewtree."
|
||||||
|
self.__progress.stop()
|
||||||
|
self.__progress.configure(mode='determinate', maximum=nodes)
|
||||||
|
|
||||||
|
def end_rm(self):
|
||||||
|
"Finish removal process by enabling operations."
|
||||||
|
self.operations_enabled = True
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Help in removing directories and files.
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __rm_dir(callback, path, rm_dir=False, rm_root=False):
|
||||||
|
"Remove directory at path, respecting the flags."
|
||||||
|
for root, dirs, files in os.walk(path, False):
|
||||||
|
# Ignore path if rm_root is false.
|
||||||
|
if rm_root or root != path:
|
||||||
|
callback()
|
||||||
|
for name in files:
|
||||||
|
file_name = os.path.join(root, name)
|
||||||
|
# Remove file while catching errors.
|
||||||
|
try:
|
||||||
|
os.remove(file_name)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
# Ignore directory if rm_dir is false.
|
||||||
|
if rm_dir:
|
||||||
|
try:
|
||||||
|
os.rmdir(root)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __rm_files(path):
|
||||||
|
"Remove files in path and get remaining space."
|
||||||
|
total_size = 0
|
||||||
|
# Find all files in directory of path.
|
||||||
|
for name in os.listdir(path):
|
||||||
|
path_name = os.path.join(path, name)
|
||||||
|
if os.path.isfile(path_name):
|
||||||
|
# Try to remove any file that may have been found.
|
||||||
|
try:
|
||||||
|
os.remove(path_name)
|
||||||
|
except OSError:
|
||||||
|
try:
|
||||||
|
# If there was an error, try to get the filesize.
|
||||||
|
total_size += os.path.getsize(path_name)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
# Return best guess of space still occupied.
|
||||||
|
return total_size
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Update the Viewtree nodes after creating a SizeTree object.
|
||||||
|
|
||||||
|
def build_tree(self, node, tree, update_node=True):
|
||||||
|
"Build the Treeview while updating the Progressbar."
|
||||||
|
self.validate_search()
|
||||||
|
if update_node:
|
||||||
|
self.sync_nodes(node, tree)
|
||||||
|
self.add_children(node, tree)
|
||||||
|
|
||||||
|
def sync_nodes(self, node, tree):
|
||||||
|
"Update attributes on node and refresh GUI."
|
||||||
|
# Copy the information on the node.
|
||||||
|
node.total_size = tree.total_size
|
||||||
|
node.file_size = tree.file_size
|
||||||
|
node.path = tree.path
|
||||||
|
# Update the Progressbar and GUI.
|
||||||
|
self.__progress.step()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def patch_tree(self, node, tree):
|
||||||
|
"Patch differences between node and tree."
|
||||||
|
node.total_size = tree.total_size
|
||||||
|
node.file_size = tree.file_size
|
||||||
|
self.patch_children(node, tree)
|
||||||
|
self.add_children(node, tree)
|
||||||
|
|
||||||
|
def add_children(self, node, tree):
|
||||||
|
"Build and traverse all child nodes."
|
||||||
|
for child in tree.children:
|
||||||
|
subnode = node.append(child.name)
|
||||||
|
self.build_tree(subnode, child)
|
||||||
|
|
||||||
|
def patch_children(self, node, tree):
|
||||||
|
"Patch Viewtree based on children of SizeTree."
|
||||||
|
for subnode in node.children:
|
||||||
|
child = tree.pop_child(subnode.name)
|
||||||
|
if child is None:
|
||||||
|
# Directory is gone.
|
||||||
|
subnode.delete()
|
||||||
|
else:
|
||||||
|
# Dig down further in tree.
|
||||||
|
self.__progress.step()
|
||||||
|
self.update()
|
||||||
|
self.patch_tree(subnode, child)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_parents(node, diff):
|
||||||
|
"Add in difference to node and parents."
|
||||||
|
while not node.root:
|
||||||
|
node.total_size += diff
|
||||||
|
node = node.parent
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Show an error when searching paths that do not exist.
|
||||||
|
|
||||||
|
def shake(self, force=False):
|
||||||
|
"Prepare to shake the application's root window."
|
||||||
|
if force:
|
||||||
|
tkinter._tkinter.setbusywaitinterval(20)
|
||||||
|
elif tkinter._tkinter.getbusywaitinterval() != 20:
|
||||||
|
# Show error message if not running at 50 FPS.
|
||||||
|
self.__error.show()
|
||||||
|
self.operations_enabled = True
|
||||||
|
return
|
||||||
|
# Shake window at 50 FPS.
|
||||||
|
self.after_idle(self.__shake)
|
||||||
|
|
||||||
|
def __shake(self, frame=0):
|
||||||
|
"Animate each step of shaking the root window."
|
||||||
|
frame += 1
|
||||||
|
# Get the window's location and update the X position.
|
||||||
|
x, y = map(int, self.__tk.geometry().split('+')[1:])
|
||||||
|
x += round(math.sin(math.pi * frame / 2.5) * \
|
||||||
|
math.sin(math.pi * frame / 50) * 5)
|
||||||
|
self.__tk.geometry('+{}+{}'.format(x, y))
|
||||||
|
if frame < 50:
|
||||||
|
# Schedule next step in the animation.
|
||||||
|
self.after(20, self.__shake, frame)
|
||||||
|
else:
|
||||||
|
# Enable operations after one second.
|
||||||
|
self.operations_enabled = True
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
class TreeviewNode:
|
||||||
|
|
||||||
|
"Interface to allow easier interaction with Treeview instance."
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def current(cls, tree):
|
||||||
|
"Take a tree view and return its currently selected node."
|
||||||
|
node = tree.selection()
|
||||||
|
return cls(tree, node[0] if node else node)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Standard Treeview Operations
|
||||||
|
|
||||||
|
__slots__ = '__tree', '__node'
|
||||||
|
|
||||||
|
def __init__(self, tree, node=''):
|
||||||
|
"Initialize the TreeviewNode object (root if node not given)."
|
||||||
|
self.__tree = tree
|
||||||
|
self.__node = node
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"Return a string representation of this node."
|
||||||
|
return '''\
|
||||||
|
NODE: {!r}
|
||||||
|
Name: {}
|
||||||
|
Total Size: {}
|
||||||
|
File Size: {}
|
||||||
|
Path {}\
|
||||||
|
'''.format(self.__node, self.name, self.total_size, self.file_size, self.path)
|
||||||
|
|
||||||
|
def insert(self, position, text):
|
||||||
|
"Insert a new node with text at position in current node."
|
||||||
|
node = self.__tree.insert(self.__node, position, text=text)
|
||||||
|
return TreeviewNode(self.__tree, node)
|
||||||
|
|
||||||
|
def append(self, text):
|
||||||
|
"Add a new node with text to the end of this node."
|
||||||
|
return self.insert(tkinter.END, text)
|
||||||
|
|
||||||
|
def move(self, parent, index):
|
||||||
|
"Insert this node under parent at index."
|
||||||
|
self.__tree.move(self.__node, parent, index)
|
||||||
|
|
||||||
|
def reattach(self, parent='', index=tkinter.END):
|
||||||
|
"Attach node to parent at index (defaults to end of root)."
|
||||||
|
self.move(parent, index)
|
||||||
|
|
||||||
|
def detach(self):
|
||||||
|
"Unlink this node from its parent but do not delete."
|
||||||
|
self.__tree.detach(self.__node)
|
||||||
|
|
||||||
|
def delete(self, get_parent=False):
|
||||||
|
"Delete this node (optionally, return parent)."
|
||||||
|
if self.__tree.exists(self.__node):
|
||||||
|
parent = self.parent if get_parent else None
|
||||||
|
self.__tree.delete(self.__node)
|
||||||
|
return parent
|
||||||
|
assert not get_parent, 'Cannot return parent!'
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Standard Treeview Properties
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root(self):
|
||||||
|
"Return if this is the root node."
|
||||||
|
return self.__node == ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent(self):
|
||||||
|
"Return the parent of this node."
|
||||||
|
return TreeviewNode(self.__tree, self.__tree.parent(self.__node))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def level(self):
|
||||||
|
"Return number of levels this node is under root."
|
||||||
|
count, node = 0, self
|
||||||
|
while not node.root:
|
||||||
|
node = node.parent
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
@property
|
||||||
|
def position(self):
|
||||||
|
"Return the position of this node in its parent."
|
||||||
|
return self.__tree.index(self.__node)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expanded(self):
|
||||||
|
"Return whether or not the node is current open."
|
||||||
|
value = self.__tree.item(self.__node, 'open')
|
||||||
|
return bool(value) and value.string == 'true'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def children(self):
|
||||||
|
"Yield back each child of this node."
|
||||||
|
for child in self.__tree.get_children(self.__node):
|
||||||
|
yield TreeviewNode(self.__tree, child)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Custom Treeview Properties
|
||||||
|
# (specific for application)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"Return the name of this node (tree column)."
|
||||||
|
return self.__tree.item(self.__node, 'text')
|
||||||
|
|
||||||
|
def __get_total_size(self):
|
||||||
|
return parse(self.__tree.set(self.__node, GUISizeTree.CLMS[0]))
|
||||||
|
|
||||||
|
def __set_total_size(self, value):
|
||||||
|
self.__tree.set(self.__node, GUISizeTree.CLMS[0], convert(value))
|
||||||
|
|
||||||
|
total_size = property(__get_total_size, __set_total_size,
|
||||||
|
doc="Total size of this node (first column)")
|
||||||
|
|
||||||
|
def __get_file_size(self):
|
||||||
|
return parse(self.__tree.set(self.__node, GUISizeTree.CLMS[1]))
|
||||||
|
|
||||||
|
def __set_file_size(self, value):
|
||||||
|
self.__tree.set(self.__node, GUISizeTree.CLMS[1], convert(value))
|
||||||
|
|
||||||
|
file_size = property(__get_file_size, __set_file_size,
|
||||||
|
doc="File size of this node (second column)")
|
||||||
|
|
||||||
|
def __get_path(self):
|
||||||
|
return self.__tree.set(self.__node, GUISizeTree.CLMS[2])
|
||||||
|
|
||||||
|
def __set_path(self, value):
|
||||||
|
self.__tree.set(self.__node, GUISizeTree.CLMS[2], value)
|
||||||
|
|
||||||
|
path = property(__get_path, __set_path,
|
||||||
|
doc="Path of this node (third column)")
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Custom Treeview Sort Order
|
||||||
|
# (specific for application)
|
||||||
|
|
||||||
|
def sort_name(self):
|
||||||
|
"If the node is open, sort its children by name."
|
||||||
|
self.__sort(lambda child: child.name)
|
||||||
|
|
||||||
|
def sort_total_size(self):
|
||||||
|
"If the node is open, sort its children by total size."
|
||||||
|
self.__sort(lambda child: child.total_size)
|
||||||
|
|
||||||
|
def sort_file_size(self):
|
||||||
|
"If the node is open, sort its children by file size."
|
||||||
|
self.__sort(lambda child: child.file_size)
|
||||||
|
|
||||||
|
def sort_path(self):
|
||||||
|
"If the node is open, sort its children by path."
|
||||||
|
self.__sort(lambda child: child.path)
|
||||||
|
|
||||||
|
def __sort(self, key):
|
||||||
|
"Sort an expanded node's children by the given key."
|
||||||
|
if self.expanded:
|
||||||
|
nodes = list(self.children)
|
||||||
|
order = sorted(nodes, key=key)
|
||||||
|
if order == nodes:
|
||||||
|
order = reversed(order)
|
||||||
|
for child in order:
|
||||||
|
self.__tree.move(child.__node, self.__node, tkinter.END)
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
class SizeTree:
|
||||||
|
|
||||||
|
"Create a tree structure outlining a directory's size."
|
||||||
|
|
||||||
|
__slots__ = 'name path children file_size total_size total_nodes'.split()
|
||||||
|
|
||||||
|
def __init__(self, callback, path):
|
||||||
|
"Initialize the SizeTree object and search the path while updating."
|
||||||
|
callback() # Allow the GUI to be updated.
|
||||||
|
head, tail = os.path.split(path)
|
||||||
|
# Create attributes for this instance.
|
||||||
|
self.name = tail or head
|
||||||
|
self.path = path
|
||||||
|
self.children = []
|
||||||
|
self.file_size = 0
|
||||||
|
self.total_size = 0
|
||||||
|
self.total_nodes = 0
|
||||||
|
# Try searching this directory.
|
||||||
|
try:
|
||||||
|
dir_list = os.listdir(path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Examine each object in this directory.
|
||||||
|
for name in dir_list:
|
||||||
|
path_name = os.path.join(path, name)
|
||||||
|
if os.path.isdir(path_name):
|
||||||
|
# Create child nodes for subdirectories.
|
||||||
|
size_tree = SizeTree(callback, path_name)
|
||||||
|
self.children.append(size_tree)
|
||||||
|
self.total_size += size_tree.total_size
|
||||||
|
self.total_nodes += size_tree.total_nodes + 1
|
||||||
|
elif os.path.isfile(path_name):
|
||||||
|
# Try getting the size of files.
|
||||||
|
try:
|
||||||
|
self.file_size += os.path.getsize(path_name)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
# Add in the total file size to the total size.
|
||||||
|
self.total_size += self.file_size
|
||||||
|
|
||||||
|
def pop_child(self, name):
|
||||||
|
"Return a named child or None if not found."
|
||||||
|
for index, child in enumerate(self.children):
|
||||||
|
if child.name == name:
|
||||||
|
return self.children.pop(index)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"Return a representation of the tree formed by this object."
|
||||||
|
lines = [self.path]
|
||||||
|
self.__walk(lines, self.children, '')
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __walk(cls, lines, children, prefix):
|
||||||
|
"Generate lines based on children and keep track of prefix."
|
||||||
|
dir_prefix, walk_prefix = prefix + '+---', prefix + '| '
|
||||||
|
for pos, neg, child in cls.__enumerate(children):
|
||||||
|
if neg == -1:
|
||||||
|
dir_prefix, walk_prefix = prefix + '\\---', prefix + ' '
|
||||||
|
lines.append(dir_prefix + child.name)
|
||||||
|
cls.__walk(lines, child.children, walk_prefix)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __enumerate(sequence):
|
||||||
|
"Generate positive and negative indices for sequence."
|
||||||
|
length = len(sequence)
|
||||||
|
for count, value in enumerate(sequence):
|
||||||
|
yield count, count - length, value
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
class Apply(tuple):
|
||||||
|
|
||||||
|
"Create a container that can run a method from its contents."
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
"Get a virtual method to map and apply to the contents."
|
||||||
|
return self.__Method(self, name)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
class __Method:
|
||||||
|
|
||||||
|
"Provide a virtual method that can be called on the array."
|
||||||
|
|
||||||
|
def __init__(self, array, name):
|
||||||
|
"Initialize the method with array and method name."
|
||||||
|
self.__array = array
|
||||||
|
self.__name = name
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
"Execute method on contents with provided arguments."
|
||||||
|
name, error, buffer = self.__name, False, []
|
||||||
|
for item in self.__array:
|
||||||
|
attr = getattr(item, name)
|
||||||
|
try:
|
||||||
|
data = attr(*args, **kwargs)
|
||||||
|
except Exception as problem:
|
||||||
|
error = problem
|
||||||
|
else:
|
||||||
|
if not error:
|
||||||
|
buffer.append(data)
|
||||||
|
if error:
|
||||||
|
raise error
|
||||||
|
return tuple(buffer)
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Provide a way of converting byte sizes into strings.
|
||||||
|
|
||||||
|
def convert(number):
|
||||||
|
"Convert bytes into human-readable representation."
|
||||||
|
if not number:
|
||||||
|
return '0 Bytes'
|
||||||
|
assert 0 < number < 1 << 110, 'number out of range'
|
||||||
|
ordered = reversed(tuple(format_bytes(partition_number(number, 1 << 10))))
|
||||||
|
cleaned = ', '.join(item for item in ordered if item[0] != '0')
|
||||||
|
return cleaned
|
||||||
|
|
||||||
|
def partition_number(number, base):
|
||||||
|
"Continually divide number by base until zero."
|
||||||
|
div, mod = divmod(number, base)
|
||||||
|
yield mod
|
||||||
|
while div:
|
||||||
|
div, mod = divmod(div, base)
|
||||||
|
yield mod
|
||||||
|
|
||||||
|
def format_bytes(parts):
|
||||||
|
"Format partitioned bytes into human-readable strings."
|
||||||
|
for power, number in enumerate(parts):
|
||||||
|
yield '{} {}'.format(number, format_suffix(power, number))
|
||||||
|
|
||||||
|
def format_suffix(power, number):
|
||||||
|
"Compute the suffix for a certain power of bytes."
|
||||||
|
return (PREFIX[power] + 'byte').capitalize() + ('s' if number != 1 else '')
|
||||||
|
|
||||||
|
PREFIX = ' kilo mega giga tera peta exa zetta yotta bronto geop'.split(' ')
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Allow conversion of byte size strings back into numbers.
|
||||||
|
|
||||||
|
def parse(string):
|
||||||
|
"Convert human-readable string back into bytes."
|
||||||
|
total = 0
|
||||||
|
for part in string.split(', '):
|
||||||
|
number, unit = part.split(' ')
|
||||||
|
s = number != '1' and 's' or ''
|
||||||
|
for power, prefix in enumerate(PREFIX):
|
||||||
|
if unit == (prefix + 'byte' + s).capitalize():
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError('{!r} not found!'.format(unit))
|
||||||
|
total += int(number) * 1 << 10 * power
|
||||||
|
return total
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Execute the main method if ran directly.
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
GUISizeTree.main()
|
||||||
120
src/misc/main.py
120
src/misc/main.py
@@ -1,120 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# Copyright (c) 2009 Andreas Balogh
|
|
||||||
# See LICENSE for details.
|
|
||||||
|
|
||||||
""" new module template """
|
|
||||||
|
|
||||||
# system imports
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import getopt
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
|
|
||||||
# constants
|
|
||||||
|
|
||||||
# globals
|
|
||||||
|
|
||||||
LOG = logging.getLogger()
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG,
|
|
||||||
format="%(asctime)s %(levelname).3s %(process)d:%(thread)d %(message)s",
|
|
||||||
datefmt="%H:%M:%S")
|
|
||||||
|
|
||||||
# definitions
|
|
||||||
|
|
||||||
class Usage(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def main(argv = [__name__]):
|
|
||||||
try:
|
|
||||||
# check for parameters
|
|
||||||
LOG.debug("starting '%s %s'", argv[0], " ".join(argv[1:]))
|
|
||||||
script_name = os.path.basename(argv[0])
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(argv[1:], "hfgp", \
|
|
||||||
["help", "force", "gui", "preview"])
|
|
||||||
except getopt.error, err:
|
|
||||||
raise Usage(err)
|
|
||||||
LOG.debug("opts: %s, args: %s", opts, args)
|
|
||||||
o_overwrite = False
|
|
||||||
o_gui = False
|
|
||||||
o_preview = False
|
|
||||||
for o, a in opts:
|
|
||||||
if o in ("-h", "--help"):
|
|
||||||
usage(script_name)
|
|
||||||
return 0
|
|
||||||
elif o in ("-f", "--force"):
|
|
||||||
o_overwrite = True
|
|
||||||
elif o in ("-p", "--preview"):
|
|
||||||
o_preview = True
|
|
||||||
elif o in ("-g", "--gui"):
|
|
||||||
o_gui = True
|
|
||||||
if len(args) == 2:
|
|
||||||
src_dir = args[0]
|
|
||||||
dest_dir = args[1]
|
|
||||||
elif len(args) == 1 :
|
|
||||||
src_dir = args[0]
|
|
||||||
dest_dir = args[0]
|
|
||||||
elif len(args) == 0 :
|
|
||||||
src_dir = None
|
|
||||||
dest_dir = None
|
|
||||||
o_gui = True
|
|
||||||
else:
|
|
||||||
raise Usage("more than two arguments provided")
|
|
||||||
# call method with appropriate arguments
|
|
||||||
if src_dir and not os.path.exists(src_dir):
|
|
||||||
raise Error("Source directory not found [%s], aborting" % (src_dir, ))
|
|
||||||
if dest_dir and not os.path.exists(dest_dir):
|
|
||||||
LOG.warn("Destination directory not found [%s]", dest_dir)
|
|
||||||
if not o_preview:
|
|
||||||
LOG.info("Creating destination directory [%s]", dest_dir)
|
|
||||||
os.makedirs(dest_dir)
|
|
||||||
if o_gui:
|
|
||||||
gui(src_dir, dest_dir, o_overwrite)
|
|
||||||
else:
|
|
||||||
cli(src_dir, dest_dir, o_preview, o_overwrite)
|
|
||||||
LOG.debug("Done.")
|
|
||||||
return 0
|
|
||||||
except Error, err:
|
|
||||||
LOG.error(err)
|
|
||||||
return 1
|
|
||||||
except Usage, err:
|
|
||||||
LOG.error(err)
|
|
||||||
LOG.info("for usage use -h or --help")
|
|
||||||
return 2
|
|
||||||
|
|
||||||
|
|
||||||
def gui(src_dir, dest_dir, o_overwrite):
|
|
||||||
""" graphical user interface """
|
|
||||||
print src_dir, dest_dir, o_overwrite
|
|
||||||
|
|
||||||
|
|
||||||
def cli(src_dir, dest_dir, o_preview, o_overwrite):
|
|
||||||
""" command line interface """
|
|
||||||
print src_dir, dest_dir, o_preview, o_overwrite
|
|
||||||
|
|
||||||
|
|
||||||
def usage(script_name):
|
|
||||||
print
|
|
||||||
print "usage: %s [options] [src_dir [dest_dir]]" % (script_name,)
|
|
||||||
print """
|
|
||||||
src_dir source directory to search for MOD/MOI
|
|
||||||
dest_dir destination directory for MPG files
|
|
||||||
options:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
-f, --force override files with same name in destination directory
|
|
||||||
-g, --gui force interactive mode
|
|
||||||
-p, --preview preview only, don't copy, don't create non-existent directories
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main(sys.argv))
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user