diff options
| -rwxr-xr-x | cli.py | 152 | ||||
| -rw-r--r-- | gui.py | 571 | ||||
| -rw-r--r-- | test/clitest.py | 12 |
3 files changed, 6 insertions, 729 deletions
| @@ -1,152 +0,0 @@ | |||
| 1 | #!/usr/bin/python | ||
| 2 | ''' | ||
| 3 | Metadata anonymisation toolkit - CLI edition | ||
| 4 | ''' | ||
| 5 | |||
| 6 | import sys | ||
| 7 | import xml.sax | ||
| 8 | import optparse | ||
| 9 | |||
| 10 | import hachoir_core | ||
| 11 | |||
| 12 | from mat import mat | ||
| 13 | |||
| 14 | __version__ = '0.1' | ||
| 15 | |||
| 16 | |||
| 17 | def parse(): | ||
| 18 | ''' | ||
| 19 | Get, and parse options passed to the program | ||
| 20 | ''' | ||
| 21 | parser = optparse.OptionParser(usage='%prog [options] files') | ||
| 22 | parser.add_option('--add2archive', '-a', action='store_true', | ||
| 23 | default=False, help='Add to outputed archive non-supported filetypes') | ||
| 24 | parser.add_option('--backup', '-b', action='store_true', default=False, | ||
| 25 | help='Keep a backup copy') | ||
| 26 | parser.add_option('--check', '-c', action='store_true', default=False, | ||
| 27 | help='Check if a file is free of harmfull metadatas') | ||
| 28 | parser.add_option('--display', '-d', action='store_true', default=False, | ||
| 29 | help='List all the meta of a file without removing them') | ||
| 30 | parser.add_option('--force', '-f', action='store_true', default=False, | ||
| 31 | help='Don\'t check if files are clean before cleaning') | ||
| 32 | parser.add_option('--list', '-l', action='store_true', default=False, | ||
| 33 | help='List all supported fileformat') | ||
| 34 | parser.add_option('--ugly', '-u', action='store_true', default=False, | ||
| 35 | help='Remove harmful meta, but loss can occure') | ||
| 36 | parser.add_option('--version', '-v', action='callback', | ||
| 37 | callback=display_version, help='Display version and exit') | ||
| 38 | |||
| 39 | values, arguments = parser.parse_args() | ||
| 40 | if not arguments and values.list is False: | ||
| 41 | parser.print_help() | ||
| 42 | sys.exit(0) | ||
| 43 | return values, arguments | ||
| 44 | |||
| 45 | |||
| 46 | def display_version(*_): | ||
| 47 | ''' | ||
| 48 | Display the program's version, and exit | ||
| 49 | ''' | ||
| 50 | print('Metadata Anonymisation Toolkit version %s') % mat.__version__ | ||
| 51 | print('CLI version %s') % __version__ | ||
| 52 | print('Hachoir version %s') % hachoir_core.__version__ | ||
| 53 | sys.exit(0) | ||
| 54 | |||
| 55 | |||
| 56 | def list_meta(class_file, filename, force): | ||
| 57 | ''' | ||
| 58 | Print all the meta of 'filename' on stdout | ||
| 59 | ''' | ||
| 60 | print('[+] File %s :' % filename) | ||
| 61 | if force is False and class_file.is_clean(): | ||
| 62 | print('No harmful meta found') | ||
| 63 | else: | ||
| 64 | meta = class_file.get_meta() | ||
| 65 | if meta is None: | ||
| 66 | print('No harmful meta found') | ||
| 67 | else: | ||
| 68 | for key, value in class_file.get_meta().iteritems(): | ||
| 69 | print(key + ' : ' + str(value)) | ||
| 70 | |||
| 71 | |||
| 72 | def is_clean(class_file, filename, force): | ||
| 73 | ''' | ||
| 74 | Say if 'filename' is clean or not | ||
| 75 | ''' | ||
| 76 | if class_file.is_clean(): | ||
| 77 | print('[+] %s is clean' % filename) | ||
| 78 | else: | ||
| 79 | print('[+] %s is not clean' % filename) | ||
| 80 | |||
| 81 | |||
| 82 | def clean_meta(class_file, filename, force): | ||
| 83 | ''' | ||
| 84 | Clean the file 'filename' | ||
| 85 | ''' | ||
| 86 | print('[+] Cleaning %s' % filename) | ||
| 87 | if force is False and class_file.is_clean(): | ||
| 88 | print('%s is already clean' % filename) | ||
| 89 | else: | ||
| 90 | class_file.remove_all() | ||
| 91 | print('%s cleaned !' % filename) | ||
| 92 | |||
| 93 | |||
| 94 | def clean_meta_ugly(class_file, filename, force): | ||
| 95 | ''' | ||
| 96 | Clean the file 'filename', ugly way | ||
| 97 | ''' | ||
| 98 | print('[+] Cleaning %s' % filename) | ||
| 99 | if force is False and class_file.is_clean(): | ||
| 100 | print('%s is already clean' % filename) | ||
| 101 | else: | ||
| 102 | class_file.remove_all_ugly() | ||
| 103 | print('%s cleaned' % filename) | ||
| 104 | |||
| 105 | |||
| 106 | def list_supported(): | ||
| 107 | ''' | ||
| 108 | Print all supported fileformat, and exit | ||
| 109 | ''' | ||
| 110 | handler = mat.XMLParser() | ||
| 111 | parser = xml.sax.make_parser() | ||
| 112 | parser.setContentHandler(handler) | ||
| 113 | with open('FORMATS', 'r') as xmlfile: | ||
| 114 | parser.parse(xmlfile) | ||
| 115 | |||
| 116 | for item in handler.list: | ||
| 117 | print('%s (%s)' % (item['name'], item['extension'])) | ||
| 118 | print('\tsupport : ' + item['support']) | ||
| 119 | print('\tmetadata : ' + item['metadata']) | ||
| 120 | print('\tmethod : ' + item['method']) | ||
| 121 | if item['support'] == 'partial': | ||
| 122 | print('\tremaining : ' + item['remaining']) | ||
| 123 | print('\n') | ||
| 124 | sys.exit(0) | ||
| 125 | |||
| 126 | |||
| 127 | def main(): | ||
| 128 | ''' | ||
| 129 | main function : get args, and launch the appropriate function | ||
| 130 | ''' | ||
| 131 | args, filenames = parse() | ||
| 132 | |||
| 133 | #func receive the function correponding to the options given as parameters | ||
| 134 | if args.display is True: # only print metadatas | ||
| 135 | func = list_meta | ||
| 136 | elif args.check is True: # only check if the file is clean | ||
| 137 | func = is_clean | ||
| 138 | elif args.ugly is True: # destructive anonymisation method | ||
| 139 | func = clean_meta_ugly | ||
| 140 | elif args.list is True: # print the list of all supported format | ||
| 141 | list_supported() | ||
| 142 | else: # clean the file | ||
| 143 | func = clean_meta | ||
| 144 | |||
| 145 | for filename in filenames: | ||
| 146 | class_file = mat.create_class_file(filename, args.backup, | ||
| 147 | args.add2archive) | ||
| 148 | if class_file is not None: | ||
| 149 | func(class_file, filename, args.force) | ||
| 150 | |||
| 151 | if __name__ == '__main__': | ||
| 152 | main() | ||
| @@ -1,571 +0,0 @@ | |||
| 1 | #!/usr/bin/env python | ||
| 2 | #charset utf-8 | ||
| 3 | |||
| 4 | ''' | ||
| 5 | Metadata anonymisation toolkit - GUI edition | ||
| 6 | ''' | ||
| 7 | |||
| 8 | import gtk | ||
| 9 | import gobject | ||
| 10 | |||
| 11 | import gettext | ||
| 12 | import locale | ||
| 13 | import logging | ||
| 14 | import os | ||
| 15 | import xml.sax | ||
| 16 | |||
| 17 | from mat import mat | ||
| 18 | |||
| 19 | __version__ = '0.1' | ||
| 20 | __author__ = 'jvoisin' | ||
| 21 | |||
| 22 | |||
| 23 | |||
| 24 | logging.basicConfig(level=mat.LOGGING_LEVEL) | ||
| 25 | |||
| 26 | |||
| 27 | class CFile(object): | ||
| 28 | ''' | ||
| 29 | Contain the "parser" class of the file "filename" | ||
| 30 | This class exist just to be "around" my parser.Generic_parser class, | ||
| 31 | since the gtk.ListStore does not accept it. | ||
| 32 | ''' | ||
| 33 | def __init__(self, filename, backup, add2archive): | ||
| 34 | try: | ||
| 35 | self.file = mat.create_class_file(filename, backup, add2archive) | ||
| 36 | except: | ||
| 37 | self.file = None | ||
| 38 | |||
| 39 | |||
| 40 | class GUI: | ||
| 41 | ''' | ||
| 42 | Main GUI class | ||
| 43 | ''' | ||
| 44 | def __init__(self): | ||
| 45 | # Preferences | ||
| 46 | self.force = False | ||
| 47 | self.backup = True | ||
| 48 | self.add2archive = True | ||
| 49 | |||
| 50 | # Main window | ||
| 51 | self.window = gtk.Window() | ||
| 52 | self.window.set_title('Metadata Anonymisation Toolkit %s' % | ||
| 53 | __version__) | ||
| 54 | self.window.connect('destroy', gtk.main_quit) | ||
| 55 | self.window.set_default_size(800, 600) | ||
| 56 | |||
| 57 | vbox = gtk.VBox() | ||
| 58 | self.window.add(vbox) | ||
| 59 | |||
| 60 | menubar = self.create_menu() | ||
| 61 | toolbar = self.create_toolbar() | ||
| 62 | content = gtk.ScrolledWindow() | ||
| 63 | vbox.pack_start(menubar, False, True, 0) | ||
| 64 | vbox.pack_start(toolbar, False, True, 0) | ||
| 65 | vbox.pack_start(content, True, True, 0) | ||
| 66 | |||
| 67 | # parser.class - name - type - cleaned | ||
| 68 | self.liststore = gtk.ListStore(object, str, str, str) | ||
| 69 | |||
| 70 | treeview = gtk.TreeView(model=self.liststore) | ||
| 71 | treeview.set_search_column(1) # name column is searchable | ||
| 72 | treeview.set_rules_hint(True) # alternate colors for rows | ||
| 73 | treeview.set_rubber_banding(True) # mouse selection | ||
| 74 | self.add_columns(treeview) | ||
| 75 | self.selection = treeview.get_selection() | ||
| 76 | self.selection.set_mode(gtk.SELECTION_MULTIPLE) | ||
| 77 | |||
| 78 | content.add(treeview) | ||
| 79 | |||
| 80 | self.statusbar = gtk.Statusbar() | ||
| 81 | self.statusbar.push(1, _('Ready')) | ||
| 82 | vbox.pack_start(self.statusbar, False, False, 0) | ||
| 83 | |||
| 84 | self.window.show_all() | ||
| 85 | |||
| 86 | def create_toolbar(self): | ||
| 87 | ''' | ||
| 88 | Returns a vbox object, which contains a toolbar with buttons | ||
| 89 | ''' | ||
| 90 | toolbar = gtk.Toolbar() | ||
| 91 | |||
| 92 | toolbutton = gtk.ToolButton(gtk.STOCK_ADD) | ||
| 93 | toolbutton.set_label(_('Add')) | ||
| 94 | toolbutton.connect('clicked', self.add_files) | ||
| 95 | toolbutton.set_tooltip_text(_('Add files')) | ||
| 96 | toolbar.add(toolbutton) | ||
| 97 | |||
| 98 | toolbutton = gtk.ToolButton(gtk.STOCK_PRINT_REPORT) | ||
| 99 | toolbutton.set_label(_('Clean')) | ||
| 100 | toolbutton.connect('clicked', self.process_files, self.mat_clean) | ||
| 101 | toolbutton.set_tooltip_text(_('Clean selected files without data loss')) | ||
| 102 | toolbar.add(toolbutton) | ||
| 103 | |||
| 104 | toolbutton = gtk.ToolButton(gtk.STOCK_PRINT_WARNING) | ||
| 105 | toolbutton.set_label(_('Brute Clean')) | ||
| 106 | toolbutton.connect('clicked', self.mat_clean_dirty) | ||
| 107 | toolbutton.set_tooltip_text(_('Clean selected files with possible \ | ||
| 108 | data loss')) | ||
| 109 | toolbar.add(toolbutton) | ||
| 110 | |||
| 111 | toolbutton = gtk.ToolButton(gtk.STOCK_FIND) | ||
| 112 | toolbutton.set_label(_('Check')) | ||
| 113 | toolbutton.connect('clicked', self.process_files, self.mat_check) | ||
| 114 | toolbutton.set_tooltip_text(_('Check selected files for harmful meta')) | ||
| 115 | toolbar.add(toolbutton) | ||
| 116 | |||
| 117 | toolbutton = gtk.ToolButton(stock_id=gtk.STOCK_QUIT) | ||
| 118 | toolbutton.set_label(_('Quit')) | ||
| 119 | toolbutton.connect('clicked', gtk.main_quit) | ||
| 120 | toolbar.add(toolbutton) | ||
| 121 | |||
| 122 | vbox = gtk.VBox(spacing=3) | ||
| 123 | vbox.pack_start(toolbar, False, False, 0) | ||
| 124 | return vbox | ||
| 125 | |||
| 126 | def add_columns(self, treeview): | ||
| 127 | ''' | ||
| 128 | Create the columns, and add them to the treeview | ||
| 129 | ''' | ||
| 130 | colname = [_('Filename'), _('Mimetype'), _('State')] | ||
| 131 | |||
| 132 | for i, j in enumerate(colname): | ||
| 133 | filename_column = gtk.CellRendererText() | ||
| 134 | column = gtk.TreeViewColumn(j, filename_column, text=i + 1) | ||
| 135 | column.set_sort_column_id(i + 1) | ||
| 136 | if i is 0: # place tooltip on this column | ||
| 137 | tips = TreeViewTooltips(column) | ||
| 138 | tips.add_view(treeview) | ||
| 139 | treeview.append_column(column) | ||
| 140 | |||
| 141 | def create_menu_item(self, name, func, menu, pix): | ||
| 142 | ''' | ||
| 143 | Create a MenuItem() like Preferences, Quit, Add, Clean, ... | ||
| 144 | ''' | ||
| 145 | item = gtk.ImageMenuItem() | ||
| 146 | picture = gtk.Image() | ||
| 147 | picture.set_from_stock(pix, gtk.ICON_SIZE_MENU) | ||
| 148 | item.set_image(picture) | ||
| 149 | item.set_label(name) | ||
| 150 | item.connect('activate', func) | ||
| 151 | menu.append(item) | ||
| 152 | |||
| 153 | def create_sub_menu(self, name, menubar): | ||
| 154 | ''' | ||
| 155 | Create a submenu like File, Edit, Clean, ... | ||
| 156 | ''' | ||
| 157 | submenu = gtk.Menu() | ||
| 158 | menuitem = gtk.MenuItem() | ||
| 159 | menuitem.set_submenu(submenu) | ||
| 160 | menuitem.set_label(name) | ||
| 161 | menubar.append(menuitem) | ||
| 162 | return submenu | ||
| 163 | |||
| 164 | def create_menu(self): | ||
| 165 | ''' | ||
| 166 | Return a MenuBar | ||
| 167 | ''' | ||
| 168 | menubar = gtk.MenuBar() | ||
| 169 | |||
| 170 | file_menu = self.create_sub_menu(_('Files'), menubar) | ||
| 171 | self.create_menu_item(_('Add files'), self.add_files, file_menu, | ||
| 172 | gtk.STOCK_ADD) | ||
| 173 | self.create_menu_item(_('Quit'), gtk.main_quit, file_menu, | ||
| 174 | gtk.STOCK_QUIT) | ||
| 175 | |||
| 176 | edit_menu = self.create_sub_menu(_('Edit'), menubar) | ||
| 177 | self.create_menu_item(_('Clear the filelist'), | ||
| 178 | lambda x: self.liststore.clear(), edit_menu, gtk.STOCK_REMOVE) | ||
| 179 | self.create_menu_item(_('Preferences'), self.preferences, edit_menu, | ||
| 180 | gtk.STOCK_PREFERENCES) | ||
| 181 | |||
| 182 | process_menu = self.create_sub_menu(_('Process'), menubar) | ||
| 183 | item = gtk.ImageMenuItem() | ||
| 184 | picture = gtk.Image() | ||
| 185 | picture.set_from_stock(gtk.STOCK_PRINT_REPORT, gtk.ICON_SIZE_MENU) | ||
| 186 | item.set_image(picture) | ||
| 187 | item.set_label(_('Clean')) | ||
| 188 | item.connect('activate', self.process_files, self.mat_clean) | ||
| 189 | process_menu.append(item) | ||
| 190 | |||
| 191 | item = gtk.ImageMenuItem() | ||
| 192 | picture = gtk.Image() | ||
| 193 | picture.set_from_stock(gtk.STOCK_PRINT_WARNING, gtk.ICON_SIZE_MENU) | ||
| 194 | item.set_image(picture) | ||
| 195 | item.set_label(_('Clean (lossy way)')) | ||
| 196 | item.connect('activate', self.process_files, self.mat_clean_dirty) | ||
| 197 | process_menu.append(item) | ||
| 198 | |||
| 199 | item = gtk.ImageMenuItem() | ||
| 200 | picture = gtk.Image() | ||
| 201 | picture.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU) | ||
| 202 | item.set_image(picture) | ||
| 203 | item.set_label(_('Check')) | ||
| 204 | item.connect('activate', self.process_files, self.mat_check) | ||
| 205 | process_menu.append(item) | ||
| 206 | |||
| 207 | |||
| 208 | help_menu = self.create_sub_menu(_('Help'), menubar) | ||
| 209 | self.create_menu_item(_('Supported formats'), self.supported, help_menu, | ||
| 210 | gtk.STOCK_INFO) | ||
| 211 | self.create_menu_item(_('About'), self.about, help_menu, gtk.STOCK_ABOUT) | ||
| 212 | |||
| 213 | return menubar | ||
| 214 | |||
| 215 | def add_files(self, button): | ||
| 216 | ''' | ||
| 217 | Add the files chosed by the filechoser ("Add" button) | ||
| 218 | ''' | ||
| 219 | chooser = gtk.FileChooserDialog(title=_('Choose files'), | ||
| 220 | parent=self.window, action=gtk.FILE_CHOOSER_ACTION_OPEN, | ||
| 221 | buttons=(gtk.STOCK_OK, 0, gtk.STOCK_CANCEL, 1)) | ||
| 222 | chooser.set_default_response(0) | ||
| 223 | chooser.set_select_multiple(True) | ||
| 224 | |||
| 225 | all_filter = gtk.FileFilter() # filter that shows all files | ||
| 226 | all_filter.set_name(_('All files')) | ||
| 227 | all_filter.add_pattern('*') | ||
| 228 | chooser.add_filter(all_filter) | ||
| 229 | |||
| 230 | supported_filter = gtk.FileFilter() | ||
| 231 | # filter that shows only supported formats | ||
| 232 | [supported_filter.add_mime_type(i) for i in mat.STRIPPERS.keys()] | ||
| 233 | supported_filter.set_name(_('Supported files')) | ||
| 234 | chooser.add_filter(supported_filter) | ||
| 235 | |||
| 236 | response = chooser.run() | ||
| 237 | |||
| 238 | if response is 0: # gtk.STOCK_OK | ||
| 239 | filenames = chooser.get_filenames() | ||
| 240 | task = self.populate(filenames) | ||
| 241 | gobject.idle_add(task.next) # asynchrone processing | ||
| 242 | chooser.destroy() | ||
| 243 | |||
| 244 | def populate(self, filenames): | ||
| 245 | ''' | ||
| 246 | Append selected files by add_file to the self.liststore | ||
| 247 | ''' | ||
| 248 | for filename in filenames: # filenames : all selected files/folders | ||
| 249 | if os.path.isdir(filename): # if "filename" is a directory | ||
| 250 | for root, dirs, files in os.walk(filename): | ||
| 251 | for item in files: | ||
| 252 | path_to_file = os.path.join(root, item) | ||
| 253 | self.add_file_to_treeview(path_to_file) | ||
| 254 | else: # filename is a regular file | ||
| 255 | self.add_file_to_treeview(filename) | ||
| 256 | yield True | ||
| 257 | yield False | ||
| 258 | |||
| 259 | def add_file_to_treeview(self, filename): | ||
| 260 | ''' | ||
| 261 | Add a file to the list if his format is supported | ||
| 262 | ''' | ||
| 263 | cf = CFile(filename, self.backup, self.add2archive) | ||
| 264 | if cf.file is not None: | ||
| 265 | self.liststore.append([cf, cf.file.basename, | ||
| 266 | cf.file.mime, _('unknow')]) | ||
| 267 | |||
| 268 | |||
| 269 | def about(self, button): | ||
| 270 | ''' | ||
| 271 | About popup | ||
| 272 | ''' | ||
| 273 | w = gtk.AboutDialog() | ||
| 274 | w.set_version(__version__) | ||
| 275 | w.set_copyright('GNU Public License v2') | ||
| 276 | w.set_comments(_('This software was coded during the GSoC 2011')) | ||
| 277 | w.set_website('https://gitweb.torproject.org/user/jvoisin/mat.git') | ||
| 278 | w.set_website_label(_('Website')) | ||
| 279 | w.set_authors(['Julien (jvoisin) Voisin', ]) | ||
| 280 | w.set_program_name('Metadata Anonymistion Toolkit') | ||
| 281 | click = w.run() | ||
| 282 | if click: | ||
| 283 | w.destroy() | ||
| 284 | |||
| 285 | def supported(self, button): | ||
| 286 | ''' | ||
| 287 | List the supported formats | ||
| 288 | ''' | ||
| 289 | dialog = gtk.Dialog(_('Supported formats'), self.window, 0, | ||
| 290 | (gtk.STOCK_CLOSE, 0)) | ||
| 291 | vbox = gtk.VBox(spacing=5) | ||
| 292 | dialog.get_content_area().pack_start(vbox, True, True, 0) | ||
| 293 | |||
| 294 | label = gtk.Label() | ||
| 295 | label.set_markup('<big><u>Supported fileformats</u></big>') | ||
| 296 | vbox.pack_start(label, True, True, 0) | ||
| 297 | |||
| 298 | #parsing xml | ||
| 299 | handler = mat.XMLParser() | ||
| 300 | parser = xml.sax.make_parser() | ||
| 301 | parser.setContentHandler(handler) | ||
| 302 | with open('FORMATS', 'r') as xmlfile: | ||
| 303 | parser.parse(xmlfile) | ||
| 304 | |||
| 305 | for item in handler.list: # list of dict : one dict per format | ||
| 306 | #create one expander per format | ||
| 307 | title = '%s (%s)' % (item['name'], item['extension']) | ||
| 308 | support = ('\t<b>%s</b> : %s' % ('support', item['support'])) | ||
| 309 | metadata = '\n\t<b>metadata</b> : ' + item['metadata'] | ||
| 310 | method = '\n\t<b>method</b> : ' + item['method'] | ||
| 311 | content = support + metadata + method | ||
| 312 | if item['support'] == 'partial': | ||
| 313 | content += '\n\t<b>remaining</b> : ' + item['remaining'] | ||
| 314 | |||
| 315 | expander = gtk.Expander(title) | ||
| 316 | vbox.pack_start(expander, False, False, 0) | ||
| 317 | label = gtk.Label() | ||
| 318 | label.set_markup(content) | ||
| 319 | expander.add(label) | ||
| 320 | |||
| 321 | dialog.show_all() | ||
| 322 | click = dialog.run() | ||
| 323 | if click is 0: # Close | ||
| 324 | dialog.destroy() | ||
| 325 | |||
| 326 | def preferences(self, button): | ||
| 327 | ''' | ||
| 328 | Preferences popup | ||
| 329 | ''' | ||
| 330 | dialog = gtk.Dialog(_('Preferences'), self.window, 0, (gtk.STOCK_OK, 0)) | ||
| 331 | hbox = gtk.HBox() | ||
| 332 | dialog.get_content_area().pack_start(hbox, False, False, 0) | ||
| 333 | |||
| 334 | icon = gtk.Image() | ||
| 335 | icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG) | ||
| 336 | hbox.pack_start(icon, False, False, 0) | ||
| 337 | |||
| 338 | table = gtk.Table(3, 2, False) # nb rows, nb lines | ||
| 339 | hbox.pack_start(table, True, True, 0) | ||
| 340 | |||
| 341 | force = gtk.CheckButton(_('Force Clean'), False) | ||
| 342 | force.set_active(self.force) | ||
| 343 | force.connect('toggled', self.invert, 'force') | ||
| 344 | force.set_tooltip_text(_('Do not check if already clean before \ | ||
| 345 | cleaning')) | ||
| 346 | table.attach(force, 0, 1, 0, 1) | ||
| 347 | |||
| 348 | backup = gtk.CheckButton(_('Backup'), False) | ||
| 349 | backup.set_active(self.backup) | ||
| 350 | backup.connect('toggled', self.invert, 'backup') | ||
| 351 | backup.set_tooltip_text(_('Keep a backup copy')) | ||
| 352 | table.attach(backup, 0, 1, 1, 2) | ||
| 353 | |||
| 354 | add2archive = gtk.CheckButton(_('Add unsupported file to archives'), | ||
| 355 | False) | ||
| 356 | add2archive.set_active(self.add2archive) | ||
| 357 | add2archive.connect('toggled', self.invert, 'add2archive') | ||
| 358 | add2archive.set_tooltip_text(_('Add non-supported (and so \ | ||
| 359 | non-anonymised) file to outputed archive')) | ||
| 360 | table.attach(add2archive, 0, 1, 2, 3) | ||
| 361 | |||
| 362 | hbox.show_all() | ||
| 363 | response = dialog.run() | ||
| 364 | if response is 0: # gtk.STOCK_OK | ||
| 365 | dialog.destroy() | ||
| 366 | |||
| 367 | def invert(self, button, name): | ||
| 368 | ''' | ||
| 369 | Invert a preference state | ||
| 370 | ''' | ||
| 371 | if name == 'force': | ||
| 372 | self.force = not self.force | ||
| 373 | elif name == 'backup': | ||
| 374 | self.backup = not self.backup | ||
| 375 | elif name == 'add2archive': | ||
| 376 | self.add2archive = not self.add2archive | ||
| 377 | |||
| 378 | def process_files(self, button, func): | ||
| 379 | ''' | ||
| 380 | Launch the function "function" in a asynchrone way | ||
| 381 | ''' | ||
| 382 | iterator = self.selection.get_selected_rows()[1] | ||
| 383 | if not iterator: # if nothing is selected : select everything | ||
| 384 | iterator = xrange(len(self.liststore)) | ||
| 385 | task = func(iterator) # launch func() in an asynchrone way | ||
| 386 | gobject.idle_add(task.next) | ||
| 387 | |||
| 388 | def mat_check(self, iterator): | ||
| 389 | ''' | ||
| 390 | Check if selected elements are clean | ||
| 391 | ''' | ||
| 392 | for line in iterator: # for each file in selection | ||
| 393 | if self.liststore[line][0].file.is_clean(): | ||
| 394 | string = _('clean') | ||
| 395 | else: | ||
| 396 | string = _('dirty') | ||
| 397 | logging.info('%s is %s' % (self.liststore[line][1], string)) | ||
| 398 | self.liststore[line][3] = string | ||
| 399 | yield True | ||
| 400 | yield False | ||
| 401 | |||
| 402 | def mat_clean(self, iterator): | ||
| 403 | ''' | ||
| 404 | Clean selected elements | ||
| 405 | ''' | ||
| 406 | for line in iterator: # for each file in selection | ||
| 407 | logging.info('Cleaning %s' % self.liststore[line][1]) | ||
| 408 | if self.liststore[line][3] is not _('clean'): | ||
| 409 | if self.force or not self.liststore[line][0].file.is_clean(): | ||
| 410 | self.liststore[line][0].file.remove_all() | ||
| 411 | self.liststore[line][3] = _('clean') | ||
| 412 | yield True | ||
| 413 | yield False | ||
| 414 | |||
| 415 | def mat_clean_dirty(self, iterator): | ||
| 416 | ''' | ||
| 417 | Clean selected elements (ugly way) | ||
| 418 | ''' | ||
| 419 | for line in iterator: # for each file in selection | ||
| 420 | logging.info('Cleaning (lossy way) %s' % self.liststore[line][1]) | ||
| 421 | if self.liststore[line][3] is not _('clean'): | ||
| 422 | if self.force or not self.liststore[line][0].file.is_clean(): | ||
| 423 | self.liststore[line][0].file.remove_all_ugly() | ||
| 424 | self.liststore[line][3] = _('clean') | ||
| 425 | yield True | ||
| 426 | yield False | ||
| 427 | |||
| 428 | |||
| 429 | class TreeViewTooltips(object): | ||
| 430 | ''' | ||
| 431 | A dirty hack losely based on treeviewtooltip from Daniel J. Popowich | ||
| 432 | (dpopowich AT astro dot umass dot edu), to display differents tooltips | ||
| 433 | for each cell of the first row of the GUI. | ||
| 434 | ''' | ||
| 435 | # Copyright (c) 2006, Daniel J. Popowich | ||
| 436 | # | ||
| 437 | # Permission is hereby granted, free of charge, to any person | ||
| 438 | # obtaining a copy of this software and associated documentation files | ||
| 439 | # (the "Software"), to deal in the Software without restriction, | ||
| 440 | # including without limitation the rights to use, copy, modify, merge, | ||
| 441 | # publish, distribute, sublicense, and/or sell copies of the Software, | ||
| 442 | # and to permit persons to whom the Software is furnished to do so, | ||
| 443 | # subject to the following conditions: | ||
| 444 | # | ||
| 445 | # The above copyright notice and this permission notice shall be | ||
| 446 | # included in all copies or substantial portions of the Software. | ||
| 447 | def __init__(self, namecol): | ||
| 448 | ''' | ||
| 449 | Initialize the tooltip. | ||
| 450 | window: the popup window that holds the tooltip text, an | ||
| 451 | instance of gtk.Window. | ||
| 452 | label: a gtk.Label that is packed into the window. The | ||
| 453 | tooltip text is set in the label with the | ||
| 454 | set_label() method, so the text can be plain or | ||
| 455 | markup text. | ||
| 456 | ''' | ||
| 457 | # create the window | ||
| 458 | self.window = window = gtk.Window(gtk.WINDOW_POPUP) | ||
| 459 | window.set_name('gtk-tooltips') | ||
| 460 | window.set_resizable(False) | ||
| 461 | window.set_border_width(4) | ||
| 462 | window.set_app_paintable(True) | ||
| 463 | window.connect("expose-event", self.__on_expose_event) | ||
| 464 | |||
| 465 | # create the label | ||
| 466 | self.label = label = gtk.Label() | ||
| 467 | label.set_line_wrap(True) | ||
| 468 | label.set_alignment(0.5, 0.5) | ||
| 469 | label.show() | ||
| 470 | window.add(label) | ||
| 471 | |||
| 472 | self.namecol = namecol | ||
| 473 | self.__save = None # saves the current cell | ||
| 474 | self.__next = None # the timer id for the next tooltip to be shown | ||
| 475 | self.__shown = False # flag on whether the tooltip window is shown | ||
| 476 | |||
| 477 | def __show(self, tooltip, x, y): | ||
| 478 | ''' | ||
| 479 | show the tooltip | ||
| 480 | ''' | ||
| 481 | self.label.set_label(tooltip) # set label | ||
| 482 | self.window.move(x, y) # move the window | ||
| 483 | self.window.show() # show it | ||
| 484 | self.__shown = True | ||
| 485 | |||
| 486 | def __hide(self): | ||
| 487 | ''' | ||
| 488 | hide the tooltip | ||
| 489 | ''' | ||
| 490 | self.__queue_next() | ||
| 491 | self.window.hide() | ||
| 492 | self.__shown = False | ||
| 493 | |||
| 494 | def __motion_handler(self, view, event): | ||
| 495 | ''' | ||
| 496 | as the pointer moves across the view, show a tooltip | ||
| 497 | ''' | ||
| 498 | path_at_pos = view.get_path_at_pos(int(event.x), int(event.y)) | ||
| 499 | if path_at_pos: | ||
| 500 | path, col, x, y = path_at_pos | ||
| 501 | tooltip = self.get_tooltip(view, col, path) | ||
| 502 | if tooltip: | ||
| 503 | tooltip = str(tooltip).strip() | ||
| 504 | self.__queue_next((path, col), tooltip, int(event.x_root), | ||
| 505 | int(event.y_root)) | ||
| 506 | return | ||
| 507 | self.__hide() | ||
| 508 | |||
| 509 | def __queue_next(self, *args): | ||
| 510 | ''' | ||
| 511 | queue next request to show a tooltip | ||
| 512 | if args is non-empty it means a request was made to show a | ||
| 513 | tooltip. if empty, no request is being made, but any | ||
| 514 | pending requests should be cancelled anyway | ||
| 515 | ''' | ||
| 516 | cell = None | ||
| 517 | |||
| 518 | if args: # if called with args, break them out | ||
| 519 | cell, tooltip, x, y = args | ||
| 520 | |||
| 521 | # if it's the same cell as previously shown, just return | ||
| 522 | if self.__save == cell: | ||
| 523 | return | ||
| 524 | |||
| 525 | if self.__next: # if we have something queued up, cancel it | ||
| 526 | gobject.source_remove(self.__next) | ||
| 527 | self.__next = None | ||
| 528 | |||
| 529 | if cell: # if there was a request | ||
| 530 | if self.__shown: # if tooltip is already shown show the new one | ||
| 531 | self.__show(tooltip, x, y) | ||
| 532 | else: # else queue it up in 1/2 second | ||
| 533 | self.__next = gobject.timeout_add(500, self.__show, | ||
| 534 | tooltip, x, y) | ||
| 535 | self.__save = cell # save this cell | ||
| 536 | |||
| 537 | def __on_expose_event(self, window, event): | ||
| 538 | ''' | ||
| 539 | this magic is required so the window appears with a 1-pixel | ||
| 540 | black border (default gtk Style). | ||
| 541 | ''' | ||
| 542 | w, h = window.size_request() | ||
| 543 | window.style.paint_flat_box(window.window, gtk.STATE_NORMAL, | ||
| 544 | gtk.SHADOW_OUT, None, window, 'tooltip', 0, 0, w, h) | ||
| 545 | |||
| 546 | def add_view(self, view): | ||
| 547 | ''' | ||
| 548 | add a gtk.TreeView to the tooltip | ||
| 549 | ''' | ||
| 550 | view.connect('motion-notify-event', self.__motion_handler) | ||
| 551 | view.connect('leave-notify-event', lambda i, j: self.__hide()) | ||
| 552 | |||
| 553 | def get_tooltip(self, view, column, path): | ||
| 554 | ''' | ||
| 555 | See the module doc string for a description of this method | ||
| 556 | ''' | ||
| 557 | if column is self.namecol: | ||
| 558 | model = view.get_model() | ||
| 559 | name = model[path][0] | ||
| 560 | return name.file.filename | ||
| 561 | |||
| 562 | |||
| 563 | if __name__ == '__main__': | ||
| 564 | #Translations | ||
| 565 | t = gettext.translation('gui', 'locale', fallback=True) | ||
| 566 | _ = t.ugettext | ||
| 567 | t.install() | ||
| 568 | |||
| 569 | #Main | ||
| 570 | GUI() | ||
| 571 | gtk.main() | ||
diff --git a/test/clitest.py b/test/clitest.py index 781fb80..c2d11af 100644 --- a/test/clitest.py +++ b/test/clitest.py | |||
| @@ -18,14 +18,14 @@ class TestRemovecli(test.MATTest): | |||
| 18 | def test_remove(self): | 18 | def test_remove(self): |
| 19 | '''make sure that the cli remove all compromizing meta''' | 19 | '''make sure that the cli remove all compromizing meta''' |
| 20 | for _, dirty in self.file_list: | 20 | for _, dirty in self.file_list: |
| 21 | subprocess.call(['../cli.py', dirty]) | 21 | subprocess.call(['../mat-cli.py', dirty]) |
| 22 | current_file = mat.create_class_file(dirty, False, True) | 22 | current_file = mat.create_class_file(dirty, False, True) |
| 23 | self.assertTrue(current_file.is_clean()) | 23 | self.assertTrue(current_file.is_clean()) |
| 24 | 24 | ||
| 25 | def test_remove_empty(self): | 25 | def test_remove_empty(self): |
| 26 | '''Test removal with clean files''' | 26 | '''Test removal with clean files''' |
| 27 | for clean, _ in self.file_list: | 27 | for clean, _ in self.file_list: |
| 28 | subprocess.call(['../cli.py', clean]) | 28 | subprocess.call(['../mat-cli.py', clean]) |
| 29 | current_file = mat.create_class_file(clean, False, True) | 29 | current_file = mat.create_class_file(clean, False, True) |
| 30 | self.assertTrue(current_file.is_clean()) | 30 | self.assertTrue(current_file.is_clean()) |
| 31 | 31 | ||
| @@ -37,7 +37,7 @@ class TestListcli(test.MATTest): | |||
| 37 | def test_list_clean(self): | 37 | def test_list_clean(self): |
| 38 | '''check if get_meta returns meta''' | 38 | '''check if get_meta returns meta''' |
| 39 | for clean, _ in self.file_list: | 39 | for clean, _ in self.file_list: |
| 40 | proc = subprocess.Popen(['../cli.py', '-d', clean], | 40 | proc = subprocess.Popen(['../mat-cli.py', '-d', clean], |
| 41 | stdout=subprocess.PIPE) | 41 | stdout=subprocess.PIPE) |
| 42 | stdout, _ = proc.communicate() | 42 | stdout, _ = proc.communicate() |
| 43 | self.assertEqual(stdout.strip('\n'), "[+] File %s :\nNo harmful \ | 43 | self.assertEqual(stdout.strip('\n'), "[+] File %s :\nNo harmful \ |
| @@ -46,7 +46,7 @@ meta found" % clean) | |||
| 46 | def test_list_dirty(self): | 46 | def test_list_dirty(self): |
| 47 | '''check if get_meta returns all the expected meta''' | 47 | '''check if get_meta returns all the expected meta''' |
| 48 | for _, dirty in self.file_list: | 48 | for _, dirty in self.file_list: |
| 49 | proc = subprocess.Popen(['../cli.py', '-d', dirty], | 49 | proc = subprocess.Popen(['../mat-cli.py', '-d', dirty], |
| 50 | stdout=subprocess.PIPE) | 50 | stdout=subprocess.PIPE) |
| 51 | stdout, _ = proc.communicate() | 51 | stdout, _ = proc.communicate() |
| 52 | self.assertNotEqual(stdout, "[+] File %s" % dirty) | 52 | self.assertNotEqual(stdout, "[+] File %s" % dirty) |
| @@ -59,7 +59,7 @@ class TestisCleancli(test.MATTest): | |||
| 59 | def test_clean(self): | 59 | def test_clean(self): |
| 60 | '''test is_clean on clean files''' | 60 | '''test is_clean on clean files''' |
| 61 | for clean, _ in self.file_list: | 61 | for clean, _ in self.file_list: |
| 62 | proc = subprocess.Popen(['../cli.py', '-c', clean], | 62 | proc = subprocess.Popen(['../mat-cli.py', '-c', clean], |
| 63 | stdout=subprocess.PIPE) | 63 | stdout=subprocess.PIPE) |
| 64 | stdout, _ = proc.communicate() | 64 | stdout, _ = proc.communicate() |
| 65 | self.assertEqual(stdout.strip('\n'), '[+] %s is clean' % clean) | 65 | self.assertEqual(stdout.strip('\n'), '[+] %s is clean' % clean) |
| @@ -67,7 +67,7 @@ class TestisCleancli(test.MATTest): | |||
| 67 | def test_dirty(self): | 67 | def test_dirty(self): |
| 68 | '''test is_clean on dirty files''' | 68 | '''test is_clean on dirty files''' |
| 69 | for _, dirty in self.file_list: | 69 | for _, dirty in self.file_list: |
| 70 | proc = subprocess.Popen(['../cli.py', '-c', dirty], | 70 | proc = subprocess.Popen(['../mat-cli.py', '-c', dirty], |
| 71 | stdout=subprocess.PIPE) | 71 | stdout=subprocess.PIPE) |
| 72 | stdout, _ = proc.communicate() | 72 | stdout, _ = proc.communicate() |
| 73 | self.assertEqual(stdout.strip('\n'), '[+] %s is not clean' % dirty) | 73 | self.assertEqual(stdout.strip('\n'), '[+] %s is not clean' % dirty) |
