From 30a19f89ca4db25c3f2669283b39e837c4b4438d Mon Sep 17 00:00:00 2001 From: jvoisin Date: Wed, 17 Aug 2011 18:26:57 +0200 Subject: Correct "binaries" names, and fix the setup.py according to the changes --- MANIFEST.in | 2 +- mat-cli | 152 ++++++++++++++++ mat-cli.py | 152 ---------------- mat-gui | 571 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ mat-gui.py | 571 ------------------------------------------------------------ setup.py | 1 + 6 files changed, 725 insertions(+), 724 deletions(-) create mode 100755 mat-cli delete mode 100755 mat-cli.py create mode 100755 mat-gui delete mode 100755 mat-gui.py diff --git a/MANIFEST.in b/MANIFEST.in index 3cdc6b9..b525485 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ -include README cli.py gui.py +include README mat-cli mat-gui graft mat recursive-include test * diff --git a/mat-cli b/mat-cli new file mode 100755 index 0000000..804c4cf --- /dev/null +++ b/mat-cli @@ -0,0 +1,152 @@ +#!/usr/bin/python +''' + Metadata anonymisation toolkit - CLI edition +''' + +import sys +import xml.sax +import optparse + +import hachoir_core + +from mat import mat + +__version__ = '0.1' + + +def parse(): + ''' + Get, and parse options passed to the program + ''' + parser = optparse.OptionParser(usage='%prog [options] files') + parser.add_option('--add2archive', '-a', action='store_true', + default=False, help='Add to outputed archive non-supported filetypes') + parser.add_option('--backup', '-b', action='store_true', default=False, + help='Keep a backup copy') + parser.add_option('--check', '-c', action='store_true', default=False, + help='Check if a file is free of harmfull metadatas') + parser.add_option('--display', '-d', action='store_true', default=False, + help='List all the meta of a file without removing them') + parser.add_option('--force', '-f', action='store_true', default=False, + help='Don\'t check if files are clean before cleaning') + parser.add_option('--list', '-l', action='store_true', default=False, + help='List all supported fileformat') + parser.add_option('--ugly', '-u', action='store_true', default=False, + help='Remove harmful meta, but loss can occure') + parser.add_option('--version', '-v', action='callback', + callback=display_version, help='Display version and exit') + + values, arguments = parser.parse_args() + if not arguments and values.list is False: + parser.print_help() + sys.exit(0) + return values, arguments + + +def display_version(*_): + ''' + Display the program's version, and exit + ''' + print('Metadata Anonymisation Toolkit version %s') % mat.__version__ + print('CLI version %s') % __version__ + print('Hachoir version %s') % hachoir_core.__version__ + sys.exit(0) + + +def list_meta(class_file, filename, force): + ''' + Print all the meta of 'filename' on stdout + ''' + print('[+] File %s :' % filename) + if force is False and class_file.is_clean(): + print('No harmful meta found') + else: + meta = class_file.get_meta() + if meta is None: + print('No harmful meta found') + else: + for key, value in class_file.get_meta().iteritems(): + print(key + ' : ' + str(value)) + + +def is_clean(class_file, filename, force): + ''' + Say if 'filename' is clean or not + ''' + if class_file.is_clean(): + print('[+] %s is clean' % filename) + else: + print('[+] %s is not clean' % filename) + + +def clean_meta(class_file, filename, force): + ''' + Clean the file 'filename' + ''' + print('[+] Cleaning %s' % filename) + if force is False and class_file.is_clean(): + print('%s is already clean' % filename) + else: + class_file.remove_all() + print('%s cleaned !' % filename) + + +def clean_meta_ugly(class_file, filename, force): + ''' + Clean the file 'filename', ugly way + ''' + print('[+] Cleaning %s' % filename) + if force is False and class_file.is_clean(): + print('%s is already clean' % filename) + else: + class_file.remove_all_ugly() + print('%s cleaned' % filename) + + +def list_supported(): + ''' + Print all supported fileformat, and exit + ''' + handler = mat.XMLParser() + parser = xml.sax.make_parser() + parser.setContentHandler(handler) + with open('FORMATS', 'r') as xmlfile: + parser.parse(xmlfile) + + for item in handler.list: + print('%s (%s)' % (item['name'], item['extension'])) + print('\tsupport : ' + item['support']) + print('\tmetadata : ' + item['metadata']) + print('\tmethod : ' + item['method']) + if item['support'] == 'partial': + print('\tremaining : ' + item['remaining']) + print('\n') + sys.exit(0) + + +def main(): + ''' + main function : get args, and launch the appropriate function + ''' + args, filenames = parse() + + #func receive the function correponding to the options given as parameters + if args.display is True: # only print metadatas + func = list_meta + elif args.check is True: # only check if the file is clean + func = is_clean + elif args.ugly is True: # destructive anonymisation method + func = clean_meta_ugly + elif args.list is True: # print the list of all supported format + list_supported() + else: # clean the file + func = clean_meta + + for filename in filenames: + class_file = mat.create_class_file(filename, args.backup, + args.add2archive) + if class_file is not None: + func(class_file, filename, args.force) + +if __name__ == '__main__': + main() diff --git a/mat-cli.py b/mat-cli.py deleted file mode 100755 index 804c4cf..0000000 --- a/mat-cli.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/python -''' - Metadata anonymisation toolkit - CLI edition -''' - -import sys -import xml.sax -import optparse - -import hachoir_core - -from mat import mat - -__version__ = '0.1' - - -def parse(): - ''' - Get, and parse options passed to the program - ''' - parser = optparse.OptionParser(usage='%prog [options] files') - parser.add_option('--add2archive', '-a', action='store_true', - default=False, help='Add to outputed archive non-supported filetypes') - parser.add_option('--backup', '-b', action='store_true', default=False, - help='Keep a backup copy') - parser.add_option('--check', '-c', action='store_true', default=False, - help='Check if a file is free of harmfull metadatas') - parser.add_option('--display', '-d', action='store_true', default=False, - help='List all the meta of a file without removing them') - parser.add_option('--force', '-f', action='store_true', default=False, - help='Don\'t check if files are clean before cleaning') - parser.add_option('--list', '-l', action='store_true', default=False, - help='List all supported fileformat') - parser.add_option('--ugly', '-u', action='store_true', default=False, - help='Remove harmful meta, but loss can occure') - parser.add_option('--version', '-v', action='callback', - callback=display_version, help='Display version and exit') - - values, arguments = parser.parse_args() - if not arguments and values.list is False: - parser.print_help() - sys.exit(0) - return values, arguments - - -def display_version(*_): - ''' - Display the program's version, and exit - ''' - print('Metadata Anonymisation Toolkit version %s') % mat.__version__ - print('CLI version %s') % __version__ - print('Hachoir version %s') % hachoir_core.__version__ - sys.exit(0) - - -def list_meta(class_file, filename, force): - ''' - Print all the meta of 'filename' on stdout - ''' - print('[+] File %s :' % filename) - if force is False and class_file.is_clean(): - print('No harmful meta found') - else: - meta = class_file.get_meta() - if meta is None: - print('No harmful meta found') - else: - for key, value in class_file.get_meta().iteritems(): - print(key + ' : ' + str(value)) - - -def is_clean(class_file, filename, force): - ''' - Say if 'filename' is clean or not - ''' - if class_file.is_clean(): - print('[+] %s is clean' % filename) - else: - print('[+] %s is not clean' % filename) - - -def clean_meta(class_file, filename, force): - ''' - Clean the file 'filename' - ''' - print('[+] Cleaning %s' % filename) - if force is False and class_file.is_clean(): - print('%s is already clean' % filename) - else: - class_file.remove_all() - print('%s cleaned !' % filename) - - -def clean_meta_ugly(class_file, filename, force): - ''' - Clean the file 'filename', ugly way - ''' - print('[+] Cleaning %s' % filename) - if force is False and class_file.is_clean(): - print('%s is already clean' % filename) - else: - class_file.remove_all_ugly() - print('%s cleaned' % filename) - - -def list_supported(): - ''' - Print all supported fileformat, and exit - ''' - handler = mat.XMLParser() - parser = xml.sax.make_parser() - parser.setContentHandler(handler) - with open('FORMATS', 'r') as xmlfile: - parser.parse(xmlfile) - - for item in handler.list: - print('%s (%s)' % (item['name'], item['extension'])) - print('\tsupport : ' + item['support']) - print('\tmetadata : ' + item['metadata']) - print('\tmethod : ' + item['method']) - if item['support'] == 'partial': - print('\tremaining : ' + item['remaining']) - print('\n') - sys.exit(0) - - -def main(): - ''' - main function : get args, and launch the appropriate function - ''' - args, filenames = parse() - - #func receive the function correponding to the options given as parameters - if args.display is True: # only print metadatas - func = list_meta - elif args.check is True: # only check if the file is clean - func = is_clean - elif args.ugly is True: # destructive anonymisation method - func = clean_meta_ugly - elif args.list is True: # print the list of all supported format - list_supported() - else: # clean the file - func = clean_meta - - for filename in filenames: - class_file = mat.create_class_file(filename, args.backup, - args.add2archive) - if class_file is not None: - func(class_file, filename, args.force) - -if __name__ == '__main__': - main() diff --git a/mat-gui b/mat-gui new file mode 100755 index 0000000..07b8d6c --- /dev/null +++ b/mat-gui @@ -0,0 +1,571 @@ +#!/usr/bin/env python +#charset utf-8 + +''' + Metadata anonymisation toolkit - GUI edition +''' + +import gtk +import gobject + +import gettext +import locale +import logging +import os +import xml.sax + +from mat import mat + +__version__ = '0.1' +__author__ = 'jvoisin' + + + +logging.basicConfig(level=mat.LOGGING_LEVEL) + + +class CFile(object): + ''' + Contain the "parser" class of the file "filename" + This class exist just to be "around" my parser.Generic_parser class, + since the gtk.ListStore does not accept it. + ''' + def __init__(self, filename, backup, add2archive): + try: + self.file = mat.create_class_file(filename, backup, add2archive) + except: + self.file = None + + +class GUI: + ''' + Main GUI class + ''' + def __init__(self): + # Preferences + self.force = False + self.backup = True + self.add2archive = True + + # Main window + self.window = gtk.Window() + self.window.set_title('Metadata Anonymisation Toolkit %s' % + __version__) + self.window.connect('destroy', gtk.main_quit) + self.window.set_default_size(800, 600) + + vbox = gtk.VBox() + self.window.add(vbox) + + menubar = self.create_menu() + toolbar = self.create_toolbar() + content = gtk.ScrolledWindow() + vbox.pack_start(menubar, False, True, 0) + vbox.pack_start(toolbar, False, True, 0) + vbox.pack_start(content, True, True, 0) + + # parser.class - name - type - cleaned + self.liststore = gtk.ListStore(object, str, str, str) + + treeview = gtk.TreeView(model=self.liststore) + treeview.set_search_column(1) # name column is searchable + treeview.set_rules_hint(True) # alternate colors for rows + treeview.set_rubber_banding(True) # mouse selection + self.add_columns(treeview) + self.selection = treeview.get_selection() + self.selection.set_mode(gtk.SELECTION_MULTIPLE) + + content.add(treeview) + + self.statusbar = gtk.Statusbar() + self.statusbar.push(1, _('Ready')) + vbox.pack_start(self.statusbar, False, False, 0) + + self.window.show_all() + + def create_toolbar(self): + ''' + Returns a vbox object, which contains a toolbar with buttons + ''' + toolbar = gtk.Toolbar() + + toolbutton = gtk.ToolButton(gtk.STOCK_ADD) + toolbutton.set_label(_('Add')) + toolbutton.connect('clicked', self.add_files) + toolbutton.set_tooltip_text(_('Add files')) + toolbar.add(toolbutton) + + toolbutton = gtk.ToolButton(gtk.STOCK_PRINT_REPORT) + toolbutton.set_label(_('Clean')) + toolbutton.connect('clicked', self.process_files, self.mat_clean) + toolbutton.set_tooltip_text(_('Clean selected files without data loss')) + toolbar.add(toolbutton) + + toolbutton = gtk.ToolButton(gtk.STOCK_PRINT_WARNING) + toolbutton.set_label(_('Brute Clean')) + toolbutton.connect('clicked', self.mat_clean_dirty) + toolbutton.set_tooltip_text(_('Clean selected files with possible \ +data loss')) + toolbar.add(toolbutton) + + toolbutton = gtk.ToolButton(gtk.STOCK_FIND) + toolbutton.set_label(_('Check')) + toolbutton.connect('clicked', self.process_files, self.mat_check) + toolbutton.set_tooltip_text(_('Check selected files for harmful meta')) + toolbar.add(toolbutton) + + toolbutton = gtk.ToolButton(stock_id=gtk.STOCK_QUIT) + toolbutton.set_label(_('Quit')) + toolbutton.connect('clicked', gtk.main_quit) + toolbar.add(toolbutton) + + vbox = gtk.VBox(spacing=3) + vbox.pack_start(toolbar, False, False, 0) + return vbox + + def add_columns(self, treeview): + ''' + Create the columns, and add them to the treeview + ''' + colname = [_('Filename'), _('Mimetype'), _('State')] + + for i, j in enumerate(colname): + filename_column = gtk.CellRendererText() + column = gtk.TreeViewColumn(j, filename_column, text=i + 1) + column.set_sort_column_id(i + 1) + if i is 0: # place tooltip on this column + tips = TreeViewTooltips(column) + tips.add_view(treeview) + treeview.append_column(column) + + def create_menu_item(self, name, func, menu, pix): + ''' + Create a MenuItem() like Preferences, Quit, Add, Clean, ... + ''' + item = gtk.ImageMenuItem() + picture = gtk.Image() + picture.set_from_stock(pix, gtk.ICON_SIZE_MENU) + item.set_image(picture) + item.set_label(name) + item.connect('activate', func) + menu.append(item) + + def create_sub_menu(self, name, menubar): + ''' + Create a submenu like File, Edit, Clean, ... + ''' + submenu = gtk.Menu() + menuitem = gtk.MenuItem() + menuitem.set_submenu(submenu) + menuitem.set_label(name) + menubar.append(menuitem) + return submenu + + def create_menu(self): + ''' + Return a MenuBar + ''' + menubar = gtk.MenuBar() + + file_menu = self.create_sub_menu(_('Files'), menubar) + self.create_menu_item(_('Add files'), self.add_files, file_menu, + gtk.STOCK_ADD) + self.create_menu_item(_('Quit'), gtk.main_quit, file_menu, + gtk.STOCK_QUIT) + + edit_menu = self.create_sub_menu(_('Edit'), menubar) + self.create_menu_item(_('Clear the filelist'), + lambda x: self.liststore.clear(), edit_menu, gtk.STOCK_REMOVE) + self.create_menu_item(_('Preferences'), self.preferences, edit_menu, + gtk.STOCK_PREFERENCES) + + process_menu = self.create_sub_menu(_('Process'), menubar) + item = gtk.ImageMenuItem() + picture = gtk.Image() + picture.set_from_stock(gtk.STOCK_PRINT_REPORT, gtk.ICON_SIZE_MENU) + item.set_image(picture) + item.set_label(_('Clean')) + item.connect('activate', self.process_files, self.mat_clean) + process_menu.append(item) + + item = gtk.ImageMenuItem() + picture = gtk.Image() + picture.set_from_stock(gtk.STOCK_PRINT_WARNING, gtk.ICON_SIZE_MENU) + item.set_image(picture) + item.set_label(_('Clean (lossy way)')) + item.connect('activate', self.process_files, self.mat_clean_dirty) + process_menu.append(item) + + item = gtk.ImageMenuItem() + picture = gtk.Image() + picture.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU) + item.set_image(picture) + item.set_label(_('Check')) + item.connect('activate', self.process_files, self.mat_check) + process_menu.append(item) + + + help_menu = self.create_sub_menu(_('Help'), menubar) + self.create_menu_item(_('Supported formats'), self.supported, help_menu, + gtk.STOCK_INFO) + self.create_menu_item(_('About'), self.about, help_menu, gtk.STOCK_ABOUT) + + return menubar + + def add_files(self, button): + ''' + Add the files chosed by the filechoser ("Add" button) + ''' + chooser = gtk.FileChooserDialog(title=_('Choose files'), + parent=self.window, action=gtk.FILE_CHOOSER_ACTION_OPEN, + buttons=(gtk.STOCK_OK, 0, gtk.STOCK_CANCEL, 1)) + chooser.set_default_response(0) + chooser.set_select_multiple(True) + + all_filter = gtk.FileFilter() # filter that shows all files + all_filter.set_name(_('All files')) + all_filter.add_pattern('*') + chooser.add_filter(all_filter) + + supported_filter = gtk.FileFilter() + # filter that shows only supported formats + [supported_filter.add_mime_type(i) for i in mat.STRIPPERS.keys()] + supported_filter.set_name(_('Supported files')) + chooser.add_filter(supported_filter) + + response = chooser.run() + + if response is 0: # gtk.STOCK_OK + filenames = chooser.get_filenames() + task = self.populate(filenames) + gobject.idle_add(task.next) # asynchrone processing + chooser.destroy() + + def populate(self, filenames): + ''' + Append selected files by add_file to the self.liststore + ''' + for filename in filenames: # filenames : all selected files/folders + if os.path.isdir(filename): # if "filename" is a directory + for root, dirs, files in os.walk(filename): + for item in files: + path_to_file = os.path.join(root, item) + self.add_file_to_treeview(path_to_file) + else: # filename is a regular file + self.add_file_to_treeview(filename) + yield True + yield False + + def add_file_to_treeview(self, filename): + ''' + Add a file to the list if his format is supported + ''' + cf = CFile(filename, self.backup, self.add2archive) + if cf.file is not None: + self.liststore.append([cf, cf.file.basename, + cf.file.mime, _('unknow')]) + + + def about(self, button): + ''' + About popup + ''' + w = gtk.AboutDialog() + w.set_version(__version__) + w.set_copyright('GNU Public License v2') + w.set_comments(_('This software was coded during the GSoC 2011')) + w.set_website('https://gitweb.torproject.org/user/jvoisin/mat.git') + w.set_website_label(_('Website')) + w.set_authors(['Julien (jvoisin) Voisin', ]) + w.set_program_name('Metadata Anonymistion Toolkit') + click = w.run() + if click: + w.destroy() + + def supported(self, button): + ''' + List the supported formats + ''' + dialog = gtk.Dialog(_('Supported formats'), self.window, 0, + (gtk.STOCK_CLOSE, 0)) + vbox = gtk.VBox(spacing=5) + dialog.get_content_area().pack_start(vbox, True, True, 0) + + label = gtk.Label() + label.set_markup('Supported fileformats') + vbox.pack_start(label, True, True, 0) + + #parsing xml + handler = mat.XMLParser() + parser = xml.sax.make_parser() + parser.setContentHandler(handler) + with open('FORMATS', 'r') as xmlfile: + parser.parse(xmlfile) + + for item in handler.list: # list of dict : one dict per format + #create one expander per format + title = '%s (%s)' % (item['name'], item['extension']) + support = ('\t%s : %s' % ('support', item['support'])) + metadata = '\n\tmetadata : ' + item['metadata'] + method = '\n\tmethod : ' + item['method'] + content = support + metadata + method + if item['support'] == 'partial': + content += '\n\tremaining : ' + item['remaining'] + + expander = gtk.Expander(title) + vbox.pack_start(expander, False, False, 0) + label = gtk.Label() + label.set_markup(content) + expander.add(label) + + dialog.show_all() + click = dialog.run() + if click is 0: # Close + dialog.destroy() + + def preferences(self, button): + ''' + Preferences popup + ''' + dialog = gtk.Dialog(_('Preferences'), self.window, 0, (gtk.STOCK_OK, 0)) + hbox = gtk.HBox() + dialog.get_content_area().pack_start(hbox, False, False, 0) + + icon = gtk.Image() + icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG) + hbox.pack_start(icon, False, False, 0) + + table = gtk.Table(3, 2, False) # nb rows, nb lines + hbox.pack_start(table, True, True, 0) + + force = gtk.CheckButton(_('Force Clean'), False) + force.set_active(self.force) + force.connect('toggled', self.invert, 'force') + force.set_tooltip_text(_('Do not check if already clean before \ +cleaning')) + table.attach(force, 0, 1, 0, 1) + + backup = gtk.CheckButton(_('Backup'), False) + backup.set_active(self.backup) + backup.connect('toggled', self.invert, 'backup') + backup.set_tooltip_text(_('Keep a backup copy')) + table.attach(backup, 0, 1, 1, 2) + + add2archive = gtk.CheckButton(_('Add unsupported file to archives'), + False) + add2archive.set_active(self.add2archive) + add2archive.connect('toggled', self.invert, 'add2archive') + add2archive.set_tooltip_text(_('Add non-supported (and so \ +non-anonymised) file to outputed archive')) + table.attach(add2archive, 0, 1, 2, 3) + + hbox.show_all() + response = dialog.run() + if response is 0: # gtk.STOCK_OK + dialog.destroy() + + def invert(self, button, name): + ''' + Invert a preference state + ''' + if name == 'force': + self.force = not self.force + elif name == 'backup': + self.backup = not self.backup + elif name == 'add2archive': + self.add2archive = not self.add2archive + + def process_files(self, button, func): + ''' + Launch the function "function" in a asynchrone way + ''' + iterator = self.selection.get_selected_rows()[1] + if not iterator: # if nothing is selected : select everything + iterator = xrange(len(self.liststore)) + task = func(iterator) # launch func() in an asynchrone way + gobject.idle_add(task.next) + + def mat_check(self, iterator): + ''' + Check if selected elements are clean + ''' + for line in iterator: # for each file in selection + if self.liststore[line][0].file.is_clean(): + string = _('clean') + else: + string = _('dirty') + logging.info('%s is %s' % (self.liststore[line][1], string)) + self.liststore[line][3] = string + yield True + yield False + + def mat_clean(self, iterator): + ''' + Clean selected elements + ''' + for line in iterator: # for each file in selection + logging.info('Cleaning %s' % self.liststore[line][1]) + if self.liststore[line][3] is not _('clean'): + if self.force or not self.liststore[line][0].file.is_clean(): + self.liststore[line][0].file.remove_all() + self.liststore[line][3] = _('clean') + yield True + yield False + + def mat_clean_dirty(self, iterator): + ''' + Clean selected elements (ugly way) + ''' + for line in iterator: # for each file in selection + logging.info('Cleaning (lossy way) %s' % self.liststore[line][1]) + if self.liststore[line][3] is not _('clean'): + if self.force or not self.liststore[line][0].file.is_clean(): + self.liststore[line][0].file.remove_all_ugly() + self.liststore[line][3] = _('clean') + yield True + yield False + + +class TreeViewTooltips(object): + ''' + A dirty hack losely based on treeviewtooltip from Daniel J. Popowich + (dpopowich AT astro dot umass dot edu), to display differents tooltips + for each cell of the first row of the GUI. + ''' + # Copyright (c) 2006, Daniel J. Popowich + # + # Permission is hereby granted, free of charge, to any person + # obtaining a copy of this software and associated documentation files + # (the "Software"), to deal in the Software without restriction, + # including without limitation the rights to use, copy, modify, merge, + # publish, distribute, sublicense, and/or sell copies of the Software, + # and to permit persons to whom the Software is furnished to do so, + # subject to the following conditions: + # + # The above copyright notice and this permission notice shall be + # included in all copies or substantial portions of the Software. + def __init__(self, namecol): + ''' + Initialize the tooltip. + window: the popup window that holds the tooltip text, an + instance of gtk.Window. + label: a gtk.Label that is packed into the window. The + tooltip text is set in the label with the + set_label() method, so the text can be plain or + markup text. + ''' + # create the window + self.window = window = gtk.Window(gtk.WINDOW_POPUP) + window.set_name('gtk-tooltips') + window.set_resizable(False) + window.set_border_width(4) + window.set_app_paintable(True) + window.connect("expose-event", self.__on_expose_event) + + # create the label + self.label = label = gtk.Label() + label.set_line_wrap(True) + label.set_alignment(0.5, 0.5) + label.show() + window.add(label) + + self.namecol = namecol + self.__save = None # saves the current cell + self.__next = None # the timer id for the next tooltip to be shown + self.__shown = False # flag on whether the tooltip window is shown + + def __show(self, tooltip, x, y): + ''' + show the tooltip + ''' + self.label.set_label(tooltip) # set label + self.window.move(x, y) # move the window + self.window.show() # show it + self.__shown = True + + def __hide(self): + ''' + hide the tooltip + ''' + self.__queue_next() + self.window.hide() + self.__shown = False + + def __motion_handler(self, view, event): + ''' + as the pointer moves across the view, show a tooltip + ''' + path_at_pos = view.get_path_at_pos(int(event.x), int(event.y)) + if path_at_pos: + path, col, x, y = path_at_pos + tooltip = self.get_tooltip(view, col, path) + if tooltip: + tooltip = str(tooltip).strip() + self.__queue_next((path, col), tooltip, int(event.x_root), + int(event.y_root)) + return + self.__hide() + + def __queue_next(self, *args): + ''' + queue next request to show a tooltip + if args is non-empty it means a request was made to show a + tooltip. if empty, no request is being made, but any + pending requests should be cancelled anyway + ''' + cell = None + + if args: # if called with args, break them out + cell, tooltip, x, y = args + + # if it's the same cell as previously shown, just return + if self.__save == cell: + return + + if self.__next: # if we have something queued up, cancel it + gobject.source_remove(self.__next) + self.__next = None + + if cell: # if there was a request + if self.__shown: # if tooltip is already shown show the new one + self.__show(tooltip, x, y) + else: # else queue it up in 1/2 second + self.__next = gobject.timeout_add(500, self.__show, + tooltip, x, y) + self.__save = cell # save this cell + + def __on_expose_event(self, window, event): + ''' + this magic is required so the window appears with a 1-pixel + black border (default gtk Style). + ''' + w, h = window.size_request() + window.style.paint_flat_box(window.window, gtk.STATE_NORMAL, + gtk.SHADOW_OUT, None, window, 'tooltip', 0, 0, w, h) + + def add_view(self, view): + ''' + add a gtk.TreeView to the tooltip + ''' + view.connect('motion-notify-event', self.__motion_handler) + view.connect('leave-notify-event', lambda i, j: self.__hide()) + + def get_tooltip(self, view, column, path): + ''' + See the module doc string for a description of this method + ''' + if column is self.namecol: + model = view.get_model() + name = model[path][0] + return name.file.filename + + +if __name__ == '__main__': + #Translations + t = gettext.translation('gui', 'locale', fallback=True) + _ = t.ugettext + t.install() + + #Main + GUI() + gtk.main() diff --git a/mat-gui.py b/mat-gui.py deleted file mode 100755 index 07b8d6c..0000000 --- a/mat-gui.py +++ /dev/null @@ -1,571 +0,0 @@ -#!/usr/bin/env python -#charset utf-8 - -''' - Metadata anonymisation toolkit - GUI edition -''' - -import gtk -import gobject - -import gettext -import locale -import logging -import os -import xml.sax - -from mat import mat - -__version__ = '0.1' -__author__ = 'jvoisin' - - - -logging.basicConfig(level=mat.LOGGING_LEVEL) - - -class CFile(object): - ''' - Contain the "parser" class of the file "filename" - This class exist just to be "around" my parser.Generic_parser class, - since the gtk.ListStore does not accept it. - ''' - def __init__(self, filename, backup, add2archive): - try: - self.file = mat.create_class_file(filename, backup, add2archive) - except: - self.file = None - - -class GUI: - ''' - Main GUI class - ''' - def __init__(self): - # Preferences - self.force = False - self.backup = True - self.add2archive = True - - # Main window - self.window = gtk.Window() - self.window.set_title('Metadata Anonymisation Toolkit %s' % - __version__) - self.window.connect('destroy', gtk.main_quit) - self.window.set_default_size(800, 600) - - vbox = gtk.VBox() - self.window.add(vbox) - - menubar = self.create_menu() - toolbar = self.create_toolbar() - content = gtk.ScrolledWindow() - vbox.pack_start(menubar, False, True, 0) - vbox.pack_start(toolbar, False, True, 0) - vbox.pack_start(content, True, True, 0) - - # parser.class - name - type - cleaned - self.liststore = gtk.ListStore(object, str, str, str) - - treeview = gtk.TreeView(model=self.liststore) - treeview.set_search_column(1) # name column is searchable - treeview.set_rules_hint(True) # alternate colors for rows - treeview.set_rubber_banding(True) # mouse selection - self.add_columns(treeview) - self.selection = treeview.get_selection() - self.selection.set_mode(gtk.SELECTION_MULTIPLE) - - content.add(treeview) - - self.statusbar = gtk.Statusbar() - self.statusbar.push(1, _('Ready')) - vbox.pack_start(self.statusbar, False, False, 0) - - self.window.show_all() - - def create_toolbar(self): - ''' - Returns a vbox object, which contains a toolbar with buttons - ''' - toolbar = gtk.Toolbar() - - toolbutton = gtk.ToolButton(gtk.STOCK_ADD) - toolbutton.set_label(_('Add')) - toolbutton.connect('clicked', self.add_files) - toolbutton.set_tooltip_text(_('Add files')) - toolbar.add(toolbutton) - - toolbutton = gtk.ToolButton(gtk.STOCK_PRINT_REPORT) - toolbutton.set_label(_('Clean')) - toolbutton.connect('clicked', self.process_files, self.mat_clean) - toolbutton.set_tooltip_text(_('Clean selected files without data loss')) - toolbar.add(toolbutton) - - toolbutton = gtk.ToolButton(gtk.STOCK_PRINT_WARNING) - toolbutton.set_label(_('Brute Clean')) - toolbutton.connect('clicked', self.mat_clean_dirty) - toolbutton.set_tooltip_text(_('Clean selected files with possible \ -data loss')) - toolbar.add(toolbutton) - - toolbutton = gtk.ToolButton(gtk.STOCK_FIND) - toolbutton.set_label(_('Check')) - toolbutton.connect('clicked', self.process_files, self.mat_check) - toolbutton.set_tooltip_text(_('Check selected files for harmful meta')) - toolbar.add(toolbutton) - - toolbutton = gtk.ToolButton(stock_id=gtk.STOCK_QUIT) - toolbutton.set_label(_('Quit')) - toolbutton.connect('clicked', gtk.main_quit) - toolbar.add(toolbutton) - - vbox = gtk.VBox(spacing=3) - vbox.pack_start(toolbar, False, False, 0) - return vbox - - def add_columns(self, treeview): - ''' - Create the columns, and add them to the treeview - ''' - colname = [_('Filename'), _('Mimetype'), _('State')] - - for i, j in enumerate(colname): - filename_column = gtk.CellRendererText() - column = gtk.TreeViewColumn(j, filename_column, text=i + 1) - column.set_sort_column_id(i + 1) - if i is 0: # place tooltip on this column - tips = TreeViewTooltips(column) - tips.add_view(treeview) - treeview.append_column(column) - - def create_menu_item(self, name, func, menu, pix): - ''' - Create a MenuItem() like Preferences, Quit, Add, Clean, ... - ''' - item = gtk.ImageMenuItem() - picture = gtk.Image() - picture.set_from_stock(pix, gtk.ICON_SIZE_MENU) - item.set_image(picture) - item.set_label(name) - item.connect('activate', func) - menu.append(item) - - def create_sub_menu(self, name, menubar): - ''' - Create a submenu like File, Edit, Clean, ... - ''' - submenu = gtk.Menu() - menuitem = gtk.MenuItem() - menuitem.set_submenu(submenu) - menuitem.set_label(name) - menubar.append(menuitem) - return submenu - - def create_menu(self): - ''' - Return a MenuBar - ''' - menubar = gtk.MenuBar() - - file_menu = self.create_sub_menu(_('Files'), menubar) - self.create_menu_item(_('Add files'), self.add_files, file_menu, - gtk.STOCK_ADD) - self.create_menu_item(_('Quit'), gtk.main_quit, file_menu, - gtk.STOCK_QUIT) - - edit_menu = self.create_sub_menu(_('Edit'), menubar) - self.create_menu_item(_('Clear the filelist'), - lambda x: self.liststore.clear(), edit_menu, gtk.STOCK_REMOVE) - self.create_menu_item(_('Preferences'), self.preferences, edit_menu, - gtk.STOCK_PREFERENCES) - - process_menu = self.create_sub_menu(_('Process'), menubar) - item = gtk.ImageMenuItem() - picture = gtk.Image() - picture.set_from_stock(gtk.STOCK_PRINT_REPORT, gtk.ICON_SIZE_MENU) - item.set_image(picture) - item.set_label(_('Clean')) - item.connect('activate', self.process_files, self.mat_clean) - process_menu.append(item) - - item = gtk.ImageMenuItem() - picture = gtk.Image() - picture.set_from_stock(gtk.STOCK_PRINT_WARNING, gtk.ICON_SIZE_MENU) - item.set_image(picture) - item.set_label(_('Clean (lossy way)')) - item.connect('activate', self.process_files, self.mat_clean_dirty) - process_menu.append(item) - - item = gtk.ImageMenuItem() - picture = gtk.Image() - picture.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU) - item.set_image(picture) - item.set_label(_('Check')) - item.connect('activate', self.process_files, self.mat_check) - process_menu.append(item) - - - help_menu = self.create_sub_menu(_('Help'), menubar) - self.create_menu_item(_('Supported formats'), self.supported, help_menu, - gtk.STOCK_INFO) - self.create_menu_item(_('About'), self.about, help_menu, gtk.STOCK_ABOUT) - - return menubar - - def add_files(self, button): - ''' - Add the files chosed by the filechoser ("Add" button) - ''' - chooser = gtk.FileChooserDialog(title=_('Choose files'), - parent=self.window, action=gtk.FILE_CHOOSER_ACTION_OPEN, - buttons=(gtk.STOCK_OK, 0, gtk.STOCK_CANCEL, 1)) - chooser.set_default_response(0) - chooser.set_select_multiple(True) - - all_filter = gtk.FileFilter() # filter that shows all files - all_filter.set_name(_('All files')) - all_filter.add_pattern('*') - chooser.add_filter(all_filter) - - supported_filter = gtk.FileFilter() - # filter that shows only supported formats - [supported_filter.add_mime_type(i) for i in mat.STRIPPERS.keys()] - supported_filter.set_name(_('Supported files')) - chooser.add_filter(supported_filter) - - response = chooser.run() - - if response is 0: # gtk.STOCK_OK - filenames = chooser.get_filenames() - task = self.populate(filenames) - gobject.idle_add(task.next) # asynchrone processing - chooser.destroy() - - def populate(self, filenames): - ''' - Append selected files by add_file to the self.liststore - ''' - for filename in filenames: # filenames : all selected files/folders - if os.path.isdir(filename): # if "filename" is a directory - for root, dirs, files in os.walk(filename): - for item in files: - path_to_file = os.path.join(root, item) - self.add_file_to_treeview(path_to_file) - else: # filename is a regular file - self.add_file_to_treeview(filename) - yield True - yield False - - def add_file_to_treeview(self, filename): - ''' - Add a file to the list if his format is supported - ''' - cf = CFile(filename, self.backup, self.add2archive) - if cf.file is not None: - self.liststore.append([cf, cf.file.basename, - cf.file.mime, _('unknow')]) - - - def about(self, button): - ''' - About popup - ''' - w = gtk.AboutDialog() - w.set_version(__version__) - w.set_copyright('GNU Public License v2') - w.set_comments(_('This software was coded during the GSoC 2011')) - w.set_website('https://gitweb.torproject.org/user/jvoisin/mat.git') - w.set_website_label(_('Website')) - w.set_authors(['Julien (jvoisin) Voisin', ]) - w.set_program_name('Metadata Anonymistion Toolkit') - click = w.run() - if click: - w.destroy() - - def supported(self, button): - ''' - List the supported formats - ''' - dialog = gtk.Dialog(_('Supported formats'), self.window, 0, - (gtk.STOCK_CLOSE, 0)) - vbox = gtk.VBox(spacing=5) - dialog.get_content_area().pack_start(vbox, True, True, 0) - - label = gtk.Label() - label.set_markup('Supported fileformats') - vbox.pack_start(label, True, True, 0) - - #parsing xml - handler = mat.XMLParser() - parser = xml.sax.make_parser() - parser.setContentHandler(handler) - with open('FORMATS', 'r') as xmlfile: - parser.parse(xmlfile) - - for item in handler.list: # list of dict : one dict per format - #create one expander per format - title = '%s (%s)' % (item['name'], item['extension']) - support = ('\t%s : %s' % ('support', item['support'])) - metadata = '\n\tmetadata : ' + item['metadata'] - method = '\n\tmethod : ' + item['method'] - content = support + metadata + method - if item['support'] == 'partial': - content += '\n\tremaining : ' + item['remaining'] - - expander = gtk.Expander(title) - vbox.pack_start(expander, False, False, 0) - label = gtk.Label() - label.set_markup(content) - expander.add(label) - - dialog.show_all() - click = dialog.run() - if click is 0: # Close - dialog.destroy() - - def preferences(self, button): - ''' - Preferences popup - ''' - dialog = gtk.Dialog(_('Preferences'), self.window, 0, (gtk.STOCK_OK, 0)) - hbox = gtk.HBox() - dialog.get_content_area().pack_start(hbox, False, False, 0) - - icon = gtk.Image() - icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG) - hbox.pack_start(icon, False, False, 0) - - table = gtk.Table(3, 2, False) # nb rows, nb lines - hbox.pack_start(table, True, True, 0) - - force = gtk.CheckButton(_('Force Clean'), False) - force.set_active(self.force) - force.connect('toggled', self.invert, 'force') - force.set_tooltip_text(_('Do not check if already clean before \ -cleaning')) - table.attach(force, 0, 1, 0, 1) - - backup = gtk.CheckButton(_('Backup'), False) - backup.set_active(self.backup) - backup.connect('toggled', self.invert, 'backup') - backup.set_tooltip_text(_('Keep a backup copy')) - table.attach(backup, 0, 1, 1, 2) - - add2archive = gtk.CheckButton(_('Add unsupported file to archives'), - False) - add2archive.set_active(self.add2archive) - add2archive.connect('toggled', self.invert, 'add2archive') - add2archive.set_tooltip_text(_('Add non-supported (and so \ -non-anonymised) file to outputed archive')) - table.attach(add2archive, 0, 1, 2, 3) - - hbox.show_all() - response = dialog.run() - if response is 0: # gtk.STOCK_OK - dialog.destroy() - - def invert(self, button, name): - ''' - Invert a preference state - ''' - if name == 'force': - self.force = not self.force - elif name == 'backup': - self.backup = not self.backup - elif name == 'add2archive': - self.add2archive = not self.add2archive - - def process_files(self, button, func): - ''' - Launch the function "function" in a asynchrone way - ''' - iterator = self.selection.get_selected_rows()[1] - if not iterator: # if nothing is selected : select everything - iterator = xrange(len(self.liststore)) - task = func(iterator) # launch func() in an asynchrone way - gobject.idle_add(task.next) - - def mat_check(self, iterator): - ''' - Check if selected elements are clean - ''' - for line in iterator: # for each file in selection - if self.liststore[line][0].file.is_clean(): - string = _('clean') - else: - string = _('dirty') - logging.info('%s is %s' % (self.liststore[line][1], string)) - self.liststore[line][3] = string - yield True - yield False - - def mat_clean(self, iterator): - ''' - Clean selected elements - ''' - for line in iterator: # for each file in selection - logging.info('Cleaning %s' % self.liststore[line][1]) - if self.liststore[line][3] is not _('clean'): - if self.force or not self.liststore[line][0].file.is_clean(): - self.liststore[line][0].file.remove_all() - self.liststore[line][3] = _('clean') - yield True - yield False - - def mat_clean_dirty(self, iterator): - ''' - Clean selected elements (ugly way) - ''' - for line in iterator: # for each file in selection - logging.info('Cleaning (lossy way) %s' % self.liststore[line][1]) - if self.liststore[line][3] is not _('clean'): - if self.force or not self.liststore[line][0].file.is_clean(): - self.liststore[line][0].file.remove_all_ugly() - self.liststore[line][3] = _('clean') - yield True - yield False - - -class TreeViewTooltips(object): - ''' - A dirty hack losely based on treeviewtooltip from Daniel J. Popowich - (dpopowich AT astro dot umass dot edu), to display differents tooltips - for each cell of the first row of the GUI. - ''' - # Copyright (c) 2006, Daniel J. Popowich - # - # Permission is hereby granted, free of charge, to any person - # obtaining a copy of this software and associated documentation files - # (the "Software"), to deal in the Software without restriction, - # including without limitation the rights to use, copy, modify, merge, - # publish, distribute, sublicense, and/or sell copies of the Software, - # and to permit persons to whom the Software is furnished to do so, - # subject to the following conditions: - # - # The above copyright notice and this permission notice shall be - # included in all copies or substantial portions of the Software. - def __init__(self, namecol): - ''' - Initialize the tooltip. - window: the popup window that holds the tooltip text, an - instance of gtk.Window. - label: a gtk.Label that is packed into the window. The - tooltip text is set in the label with the - set_label() method, so the text can be plain or - markup text. - ''' - # create the window - self.window = window = gtk.Window(gtk.WINDOW_POPUP) - window.set_name('gtk-tooltips') - window.set_resizable(False) - window.set_border_width(4) - window.set_app_paintable(True) - window.connect("expose-event", self.__on_expose_event) - - # create the label - self.label = label = gtk.Label() - label.set_line_wrap(True) - label.set_alignment(0.5, 0.5) - label.show() - window.add(label) - - self.namecol = namecol - self.__save = None # saves the current cell - self.__next = None # the timer id for the next tooltip to be shown - self.__shown = False # flag on whether the tooltip window is shown - - def __show(self, tooltip, x, y): - ''' - show the tooltip - ''' - self.label.set_label(tooltip) # set label - self.window.move(x, y) # move the window - self.window.show() # show it - self.__shown = True - - def __hide(self): - ''' - hide the tooltip - ''' - self.__queue_next() - self.window.hide() - self.__shown = False - - def __motion_handler(self, view, event): - ''' - as the pointer moves across the view, show a tooltip - ''' - path_at_pos = view.get_path_at_pos(int(event.x), int(event.y)) - if path_at_pos: - path, col, x, y = path_at_pos - tooltip = self.get_tooltip(view, col, path) - if tooltip: - tooltip = str(tooltip).strip() - self.__queue_next((path, col), tooltip, int(event.x_root), - int(event.y_root)) - return - self.__hide() - - def __queue_next(self, *args): - ''' - queue next request to show a tooltip - if args is non-empty it means a request was made to show a - tooltip. if empty, no request is being made, but any - pending requests should be cancelled anyway - ''' - cell = None - - if args: # if called with args, break them out - cell, tooltip, x, y = args - - # if it's the same cell as previously shown, just return - if self.__save == cell: - return - - if self.__next: # if we have something queued up, cancel it - gobject.source_remove(self.__next) - self.__next = None - - if cell: # if there was a request - if self.__shown: # if tooltip is already shown show the new one - self.__show(tooltip, x, y) - else: # else queue it up in 1/2 second - self.__next = gobject.timeout_add(500, self.__show, - tooltip, x, y) - self.__save = cell # save this cell - - def __on_expose_event(self, window, event): - ''' - this magic is required so the window appears with a 1-pixel - black border (default gtk Style). - ''' - w, h = window.size_request() - window.style.paint_flat_box(window.window, gtk.STATE_NORMAL, - gtk.SHADOW_OUT, None, window, 'tooltip', 0, 0, w, h) - - def add_view(self, view): - ''' - add a gtk.TreeView to the tooltip - ''' - view.connect('motion-notify-event', self.__motion_handler) - view.connect('leave-notify-event', lambda i, j: self.__hide()) - - def get_tooltip(self, view, column, path): - ''' - See the module doc string for a description of this method - ''' - if column is self.namecol: - model = view.get_model() - name = model[path][0] - return name.file.filename - - -if __name__ == '__main__': - #Translations - t = gettext.translation('gui', 'locale', fallback=True) - _ = t.ugettext - t.install() - - #Main - GUI() - gtk.main() diff --git a/setup.py b/setup.py index 35d282e..8fdd923 100755 --- a/setup.py +++ b/setup.py @@ -10,4 +10,5 @@ setup(name='MAT', license='GPLv2', url='https://gitweb.torproject.org/user/jvoisin/mat.git', packages=['mat'], + script=['mat-cli', 'mat-gui'] ) -- cgit v1.3