#!/usr/bin/env python # -*- coding: utf-8 -*- """This module adds a menu item to the nautilus right-click menu which allows to move all the selected files/folders to a browsed destination just through the right-clicking NOTE: REQUIRES NAUTILUS PYEXTENSIONS >= 1.0.5 INSTALLED""" # move-to-browsed-place.py version 1.0.5 # # Copyright 2008-2010 Giuseppe Penone # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. import pygtk pygtk.require('2.0') import gtk, gobject import nautilus, gconf, urllib, os, time, pickle, subprocess NAUTILUS_MENU_LABEL = 'Move to Browsed Place' NAUTILUS_MENU_HELP = 'Move the selected file(s)/folder(s) to a browsed place' GLADE_PATH = '/usr/share/nautilus-pyextensions/glade/nautilus-pyextensions_copymove.glade' COPYMOVE_PATH = os.path.join(os.path.expanduser('~'), '.config/nautilus-pyextensions/copymove.pkl') def dialog_error(message, dialog_title = "Error..."): """The extension's error dialog""" dialog = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK, message_format=message) dialog.set_title(dialog_title) dialog.run() dialog.destroy() def choose_dir_dialog(curr_folder = None, dialog_title = "Choose a Directory..."): """The extension's save file as dialog, returns the retrieved filepath or None""" chooser = gtk.FileChooserDialog( action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK) ) if curr_folder == None or os.path.isdir(curr_folder) == False: chooser.set_current_folder(os.path.expanduser('~')) else: chooser.set_current_folder(curr_folder) chooser.set_title(dialog_title) if chooser.run() == gtk.RESPONSE_OK: dirpath = chooser.get_current_folder() chooser.destroy() return dirpath else: chooser.destroy() return None class GladeWidgetsWrapper: """Handles the retrieval of glade widgets""" def __init__(self, glade_file_path, gui_instance): try: self.glade_widgets = gtk.Builder() self.glade_widgets.add_from_file(glade_file_path) self.glade_widgets.connect_signals(gui_instance) except: print "Failed to load the glade file" def __getitem__(self, key): """Gives us the ability to do: wrapper['widget_name'].action()""" return self.glade_widgets.get_object(key) def __getattr__(self, attr): """Gives us the ability to do: wrapper.widget_name.action()""" new_widget = self.glade_widgets.get_object(attr) if new_widget is None: raise AttributeError, 'Widget %r not found' % attr setattr(self, attr, new_widget) return new_widget class MoveToBrowsedPlace(nautilus.MenuProvider): """Implements the 'Move to Browsed Place' extension to the nautilus right-click menu""" def __init__(self): """Creates a GConfClient (Gnome Configuration Client)""" self.client = gconf.client_get_default() def open_dialog(self, menu, selected_items): """Opens the dialog to browse for a destination, afterwards launchs the move of the passed items to the selected destination""" self.glade = GladeWidgetsWrapper(GLADE_PATH, self) destination_root = choose_dir_dialog(dialog_title="Choose the destination") if destination_root == None: return if not os.access(destination_root, os.W_OK): dialog_error("Error: you have no write access\nto %s" % destination_root) return if destination_root == os.path.dirname(urllib.unquote(selected_items[0].get_uri()[7:])): dialog_error("Error: destination and source are the same") return source_size = 0 self.config_data = {'source_path_list':[], 'source_path_size':[], 'dest_path_list':[], 'empty_dirs':[]} self.replace_state = 3 # init replace_state as NO REPLACE self.glade.overw_all_checkbutton.set_active(False) for sel_item in selected_items: source_path = urllib.unquote(sel_item.get_uri()[7:]) if os.path.isfile(source_path): basename = os.path.basename(source_path) source_size += os.path.getsize(source_path) destination_path = os.path.join(destination_root, basename) self.single_file_process(source_path, source_size, destination_path, destination_root) else: for dirpath, dirnames, filenames in os.walk(source_path): if not dirpath in self.config_data['empty_dirs']: self.config_data['empty_dirs'].append(dirpath) for filename in filenames: file_path = os.path.join(dirpath, filename) basename = os.path.basename(file_path) source_size += os.path.getsize(file_path) nested_dirs = dirpath.replace(source_path, "") if len(nested_dirs)>0 and nested_dirs[0] == "/": nested_dirs = nested_dirs[1:] destination_path = os.path.join(destination_root, # the directory where the source folder is os.path.basename(source_path), # the sorce folder nested_dirs, # eventually nested folders basename) # the file name self.single_file_process(file_path, source_size, destination_path, os.path.dirname(destination_path)) # check for the free size available if len(self.config_data['source_path_list'])>0: destination_size = os.statvfs(destination_root).f_bavail * os.statvfs(destination_root).f_bsize if source_size > destination_size: dialog_error("Error: there is not enaugh space in the chosen destination:\nAvailable: %s MB\nNeeded: %s MB" % (destination_size/(1024*1024), source_size/(1024*1024))) return # MOVE BEGINS... config_file_descriptor = file(COPYMOVE_PATH, 'w') pickle.dump(self.config_data, config_file_descriptor) config_file_descriptor.close() subprocess.call("/usr/share/nautilus-pyextensions/nautilus-pyextensions_move &", shell=True) def single_file_process(self, source_path, source_size, destination_path, destination_dir): """Single File Handling""" if os.path.exists(destination_path): basename = os.path.basename(destination_path) # 6:"RENAME ALL", 5:"RENAME", 4:"NO to ALL", 3:"NO", 2:"YES to ALL", 1:"YES" if self.glade.overw_all_checkbutton.get_active() == False: self.glade.overw_replace_filename.set_text('A file named "%s"\nalready exists.\nDo you want to replace it?' % basename) self.glade.overw_replace_filename.set_use_markup(True) self.glade.overw_replace_filedir.set_text('\nThe file already exists\nin "%s".\nReplacing it will overwrite its content.\n ' % destination_dir) self.glade.overw_replace_filedir.set_use_markup(True) self.glade.overw_replacer_info.set_text("Last modified\n%s" % time.ctime(os.path.getmtime(source_path))) self.glade.overw_replacer_info.set_use_markup(True) self.glade.overw_replaced_info.set_text("Last modified\n%s" % time.ctime(os.path.getmtime(destination_path))) self.glade.overw_replaced_info.set_use_markup(True) self.replace_state = self.glade.dialog_exists.run() self.glade.dialog_exists.hide() if self.replace_state == 1: os.remove(destination_path) # REPLACE THIS elif self.replace_state == 3: return # SKIP THIS elif self.replace_state == 5: # RENAME THIS: the old file/folder is renamed, the new file/folder will keep the correct name new_basename = "old." + basename while os.path.exists(os.path.join(destination_dir, new_basename)) == True: new_basename = "old." + new_basename os.rename(destination_path, os.path.join(destination_dir, new_basename)) else: dialog_error("software error") self.config_data['source_path_list'].append(source_path) self.config_data['source_path_size'].append(source_size) self.config_data['dest_path_list'].append(destination_path) def get_file_items(self, window, selected_items): """Adds the 'Move to Browsed Place' menu item to the nautilus right-click menu, connects its 'activate' signal to the 'open_dialog' method passing the list of selected items""" if selected_items and (selected_items[0].get_uri_scheme() != 'file' or not os.access(urllib.unquote(selected_items[0].get_parent_uri()[7:]), os.W_OK)): return item = nautilus.MenuItem('NautilusPython::gtk-cut', NAUTILUS_MENU_LABEL, NAUTILUS_MENU_HELP) item.set_property('icon', 'gtk-cut') item.connect('activate', self.open_dialog, selected_items) return item,