Ejemplo n.º 1
0
    def __init__(self):

        # The super core
        self.aafm = Aafm('adb', os.getcwd(), '/mnt/sdcard/')
        self.queue = []

        self.basedir = os.path.dirname(os.path.abspath(__file__))

        if os.name == 'nt':
            self.get_owner = self._get_owner_windows
            self.get_group = self._get_group_windows
        else:
            self.get_owner = self._get_owner
            self.get_group = self._get_group

        # Build main window from XML
        builder = gtk.Builder()
        builder.add_from_file(
            os.path.join(self.basedir, "data/glade/interface.xml"))
        builder.connect_signals({"on_window_destroy": gtk.main_quit})
        self.window = builder.get_object("window")

        imageDir = gtk.Image()
        imageDir.set_from_file(
            os.path.join(self.basedir, './data/icons/folder.png'))
        imageFile = gtk.Image()
        imageFile.set_from_file(
            os.path.join(self.basedir, './data/icons/file.png'))

        # Host and device TreeViews

        # HOST
        self.host_treeViewFile = TreeViewFile(imageDir.get_pixbuf(),
                                              imageFile.get_pixbuf())

        hostFrame = builder.get_object('frameHost')
        hostFrame.get_child().add(self.host_treeViewFile.get_view())

        hostTree = self.host_treeViewFile.get_tree()
        hostTree.connect('row-activated', self.host_navigate_callback)
        hostTree.connect('button_press_event',
                         self.on_host_tree_view_contextual_menu)

        host_targets = [('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
                        ('ADB_text', 0, 1), ('text/plain', 0, 2)]

        hostTree.enable_model_drag_dest(
            host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        hostTree.connect('drag-data-received', self.on_host_drag_data_received)

        hostTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK, host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        hostTree.connect('drag_data_get', self.on_host_drag_data_get)

        self.hostFrame = hostFrame
        self.hostName = socket.gethostname()

        # DEVICE
        self.device_treeViewFile = TreeViewFile(imageDir.get_pixbuf(),
                                                imageFile.get_pixbuf())

        deviceFrame = builder.get_object('frameDevice')
        deviceFrame.get_child().add(self.device_treeViewFile.get_view())

        deviceTree = self.device_treeViewFile.get_tree()
        deviceTree.connect('row-activated', self.device_navigate_callback)
        deviceTree.connect('button_press_event',
                           self.on_device_tree_view_contextual_menu)

        device_targets = [('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
                          ('ADB_text', 0, 1), ('XdndDirectSave0', 0, 2),
                          ('text/plain', 0, 3)]

        deviceTree.enable_model_drag_dest(
            device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        deviceTree.connect('drag-data-received',
                           self.on_device_drag_data_received)

        deviceTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK, device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        deviceTree.connect('drag-data-get', self.on_device_drag_data_get)
        deviceTree.connect('drag-begin', self.on_device_drag_begin)

        self.deviceFrame = deviceFrame

        # Progress bar
        self.progress_bar = builder.get_object('progressBar')

        # Some more subtle details...
        self.window.set_title("Android ADB file manager")
        #self.adb = 'adb'
        self.host_cwd = os.getcwd()
        self.device_cwd = '/mnt/sdcard/'

        self.refresh_host_files()
        self.refresh_device_files()

        # Make both panels equal in size (at least initially)
        panelsPaned = builder.get_object('panelsPaned')
        panelW, panelH = panelsPaned.size_request()
        halfW = panelW / 2
        panelsPaned.set_position(halfW)

        # And we're done!
        self.window.show_all()
Ejemplo n.º 2
0
	def __init__(self):
		
		# The super core
		self.aafm = Aafm('adb', os.getcwd(), '/mnt/sdcard/')
		self.queue = []

		self.basedir = os.path.dirname(os.path.abspath(__file__))
		
		if os.name == 'nt':
			self.get_owner = self._get_owner_windows
			self.get_group = self._get_group_windows
		else:
			self.get_owner = self._get_owner
			self.get_group = self._get_group

		# Build main window from XML
		builder = gtk.Builder()
		builder.add_from_file(os.path.join(self.basedir, "data/glade/interface.xml"))
		builder.connect_signals({ "on_window_destroy" : gtk.main_quit })
		self.window = builder.get_object("window")

		imageDir = gtk.Image()
		imageDir.set_from_file(os.path.join(self.basedir, './data/icons/folder.png'))
		imageFile = gtk.Image()
		imageFile.set_from_file(os.path.join(self.basedir, './data/icons/file.png'))
		
		# Show hidden files and folders
		self.showHidden = False

		showHidden = builder.get_object('showHidden')
		showHidden.connect('toggled', self.on_toggle_hidden)

		# Refresh view
		refreshView = builder.get_object('refreshView')
		refreshView.connect('activate', self.refresh_all)

		itemQuit = builder.get_object('itemQuit')
		itemQuit.connect('activate', gtk.main_quit)

		self.menuDevices = builder.get_object('menuDevices')
		self.refresh_menu_devices()

		# Host and device TreeViews
		
		# HOST
		self.host_treeViewFile = TreeViewFile(imageDir.get_pixbuf(), imageFile.get_pixbuf())
		
		hostFrame = builder.get_object('frameHost')
		hostFrame.get_child().add(self.host_treeViewFile.get_view())
		
		hostTree = self.host_treeViewFile.get_tree()
		hostTree.connect('row-activated', self.host_navigate_callback)
		hostTree.connect('button_press_event', self.on_host_tree_view_contextual_menu)
	
		host_targets = [
			('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
			('ADB_text', 0, 1),
			('text/plain', 0, 2)
		]

		hostTree.enable_model_drag_dest(
			host_targets,
			gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
		)
		hostTree.connect('drag-data-received', self.on_host_drag_data_received)

		hostTree.enable_model_drag_source(
			gtk.gdk.BUTTON1_MASK,
			host_targets,
			gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
		)
		hostTree.connect('drag_data_get', self.on_host_drag_data_get)
		
		self.hostFrame = hostFrame
		self.hostName = socket.gethostname()


		# DEVICE
		self.device_treeViewFile = TreeViewFile(imageDir.get_pixbuf(), imageFile.get_pixbuf())
		
		deviceFrame = builder.get_object('frameDevice')
		deviceFrame.get_child().add(self.device_treeViewFile.get_view())

		deviceTree = self.device_treeViewFile.get_tree()
		deviceTree.connect('row-activated', self.device_navigate_callback)
		deviceTree.connect('button_press_event', self.on_device_tree_view_contextual_menu)

		device_targets = [
			('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
			('ADB_text', 0, 1),
			('XdndDirectSave0', 0, 2),
			('text/plain', 0, 3)
		]

		deviceTree.enable_model_drag_dest(
			device_targets,
			gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
		)
		deviceTree.connect('drag-data-received', self.on_device_drag_data_received)
		
		deviceTree.enable_model_drag_source(
			gtk.gdk.BUTTON1_MASK,
			device_targets,
			gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
		)
		deviceTree.connect('drag-data-get', self.on_device_drag_data_get)
		deviceTree.connect('drag-begin', self.on_device_drag_begin)
		
		self.deviceFrame = deviceFrame


		# Progress bar
		self.progress_bar = builder.get_object('progressBar')

		# Some more subtle details...
		self.window.set_title("Android ADB file manager")
		#self.adb = 'adb'
		self.host_cwd = os.getcwd()
		self.aafm.set_device_cwd('/mnt/sdcard/')

		self.refresh_all()

		# Make both panels equal in size (at least initially)
		panelsPaned = builder.get_object('panelsPaned')
		self.window.show_all()
		panelsPaned.set_position(panelsPaned.get_allocation().width / 2)

		# And we're done!
		self.window.show_all()
Ejemplo n.º 3
0
class Aafm_GUI:

    QUEUE_ACTION_COPY_TO_DEVICE = 'copy_to_device'
    QUEUE_ACTION_COPY_FROM_DEVICE = 'copy_from_device'
    QUEUE_ACTION_MOVE_IN_DEVICE = 'move_in_device'
    QUEUE_ACTION_MOVE_IN_HOST = 'move_in_host'

    # These constants are for dragging files to Nautilus
    XDS_ATOM = gtk.gdk.atom_intern("XdndDirectSave0")
    TEXT_ATOM = gtk.gdk.atom_intern("text/plain")
    XDS_FILENAME = 'whatever.txt'

    def __init__(self):

        # The super core
        self.aafm = Aafm('adb', os.getcwd(), '/mnt/sdcard/')
        self.queue = []

        self.basedir = os.path.dirname(os.path.abspath(__file__))

        if os.name == 'nt':
            self.get_owner = self._get_owner_windows
            self.get_group = self._get_group_windows
        else:
            self.get_owner = self._get_owner
            self.get_group = self._get_group

        # Build main window from XML
        builder = gtk.Builder()
        builder.add_from_file(
            os.path.join(self.basedir, "data/glade/interface.xml"))
        builder.connect_signals({"on_window_destroy": gtk.main_quit})
        self.window = builder.get_object("window")

        imageDir = gtk.Image()
        imageDir.set_from_file(
            os.path.join(self.basedir, './data/icons/folder.png'))
        imageFile = gtk.Image()
        imageFile.set_from_file(
            os.path.join(self.basedir, './data/icons/file.png'))

        # Host and device TreeViews

        # HOST
        self.host_treeViewFile = TreeViewFile(imageDir.get_pixbuf(),
                                              imageFile.get_pixbuf())

        hostFrame = builder.get_object('frameHost')
        hostFrame.get_child().add(self.host_treeViewFile.get_view())

        hostTree = self.host_treeViewFile.get_tree()
        hostTree.connect('row-activated', self.host_navigate_callback)
        hostTree.connect('button_press_event',
                         self.on_host_tree_view_contextual_menu)

        host_targets = [('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
                        ('ADB_text', 0, 1), ('text/plain', 0, 2)]

        hostTree.enable_model_drag_dest(
            host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        hostTree.connect('drag-data-received', self.on_host_drag_data_received)

        hostTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK, host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        hostTree.connect('drag_data_get', self.on_host_drag_data_get)

        self.hostFrame = hostFrame
        self.hostName = socket.gethostname()

        # DEVICE
        self.device_treeViewFile = TreeViewFile(imageDir.get_pixbuf(),
                                                imageFile.get_pixbuf())

        deviceFrame = builder.get_object('frameDevice')
        deviceFrame.get_child().add(self.device_treeViewFile.get_view())

        deviceTree = self.device_treeViewFile.get_tree()
        deviceTree.connect('row-activated', self.device_navigate_callback)
        deviceTree.connect('button_press_event',
                           self.on_device_tree_view_contextual_menu)

        device_targets = [('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
                          ('ADB_text', 0, 1), ('XdndDirectSave0', 0, 2),
                          ('text/plain', 0, 3)]

        deviceTree.enable_model_drag_dest(
            device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        deviceTree.connect('drag-data-received',
                           self.on_device_drag_data_received)

        deviceTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK, device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        deviceTree.connect('drag-data-get', self.on_device_drag_data_get)
        deviceTree.connect('drag-begin', self.on_device_drag_begin)

        self.deviceFrame = deviceFrame

        # Progress bar
        self.progress_bar = builder.get_object('progressBar')

        # Some more subtle details...
        self.window.set_title("Android ADB file manager")
        #self.adb = 'adb'
        self.host_cwd = os.getcwd()
        self.device_cwd = '/mnt/sdcard/'

        self.refresh_host_files()
        self.refresh_device_files()

        # Make both panels equal in size (at least initially)
        panelsPaned = builder.get_object('panelsPaned')
        panelW, panelH = panelsPaned.size_request()
        halfW = panelW / 2
        panelsPaned.set_position(halfW)

        # And we're done!
        self.window.show_all()

    def host_navigate_callback(self, widget, path, view_column):

        row = path[0]
        model = widget.get_model()
        iter = model.get_iter(row)
        is_dir = model.get_value(iter, 0)
        name = model.get_value(iter, 1)

        if is_dir:
            self.host_cwd = os.path.normpath(os.path.join(self.host_cwd, name))
            self.aafm.set_host_cwd(self.host_cwd)
            self.refresh_host_files()

    def device_navigate_callback(self, widget, path, view_column):

        row = path[0]
        model = widget.get_model()
        iter = model.get_iter(row)
        is_dir = model.get_value(iter, 0)
        name = model.get_value(iter, 1)

        if is_dir:
            self.device_cwd = self.aafm.device_path_normpath(
                self.aafm.device_path_join(self.device_cwd, name))
            self.aafm.set_device_cwd(self.device_cwd)
            self.refresh_device_files()

    def refresh_host_files(self):
        self.host_treeViewFile.load_data(self.dir_scan_host(self.host_cwd))
        self.hostFrame.set_label('%s:%s' % (self.hostName, self.host_cwd))

    def refresh_device_files(self):
        self.device_treeViewFile.load_data(
            self.dir_scan_device(self.device_cwd))
        self.deviceFrame.set_label('%s:%s' % ('device', self.device_cwd))

    def get_treeviewfile_selected(self, treeviewfile):
        values = []
        model, rows = treeviewfile.get_tree().get_selection(
        ).get_selected_rows()

        for row in rows:
            iter = model.get_iter(row)
            filename = model.get_value(iter, 1)
            is_directory = model.get_value(iter, 0)
            values.append({'filename': filename, 'is_directory': is_directory})

        return values

    def get_host_selected_files(self):
        return self.get_treeviewfile_selected(self.host_treeViewFile)

    def get_device_selected_files(self):
        return self.get_treeviewfile_selected(self.device_treeViewFile)

    """ Walks through a directory and return the data in a tree-style list 
		that can be used by the TreeViewFile """

    def dir_scan_host(self, directory):
        output = []

        root, dirs, files = next(os.walk(directory))

        dirs.sort()
        files.sort()

        output.append({
            'directory': True,
            'name': '..',
            'size': 0,
            'timestamp': '',
            'permissions': '',
            'owner': '',
            'group': ''
        })

        for d in dirs:
            path = os.path.join(directory, d)
            output.append({
                'directory':
                True,
                'name':
                d,
                'size':
                0,
                'timestamp':
                self.format_timestamp(os.path.getmtime(path)),
                'permissions':
                self.get_permissions(path),
                'owner':
                self.get_owner(path),
                'group':
                self.get_group(path)
            })

        for f in files:
            path = os.path.join(directory, f)
            size = os.path.getsize(path)
            output.append({
                'directory':
                False,
                'name':
                f,
                'size':
                size,
                'timestamp':
                self.format_timestamp(os.path.getmtime(path)),
                'permissions':
                self.get_permissions(path),
                'owner':
                self.get_owner(path),
                'group':
                self.get_group(path)
            })

        return output

    """ The following three methods are probably NOT the best way of doing things.
	At least according to all the warnings that say os.stat is very costly
	and should be cached."""

    def get_permissions(self, filename):
        st = os.stat(filename)
        mode = st.st_mode
        permissions = ''

        bits = [
            stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, stat.S_IRGRP,
            stat.S_IWGRP, stat.S_IXGRP, stat.S_IROTH, stat.S_IWOTH,
            stat.S_IXOTH
        ]

        attrs = ['r', 'w', 'x']

        for i in range(0, len(bits)):
            bit = bits[i]
            attr = attrs[i % len(attrs)]

            if bit & mode:
                permissions += attr
            else:
                permissions += '-'

        return permissions

    def _get_owner(self, filename):
        st = os.stat(filename)
        uid = st.st_uid
        try:
            user = pwd.getpwuid(uid)[0]
        except KeyError:
            print('unknown uid %d for file %s' % (uid, filename))
            user = '******'
        return user

    def _get_owner_windows(self, filename):
        sd = win32security.GetFileSecurity(
            filename, win32security.OWNER_SECURITY_INFORMATION)
        owner_sid = sd.GetSecurityDescriptorOwner()
        name, domain, type = win32security.LookupAccountSid(None, owner_sid)
        return name

    def _get_group(self, filename):
        st = os.stat(filename)
        gid = st.st_gid
        try:
            groupname = grp.getgrgid(gid)[0]
        except KeyError:
            print('unknown gid %d for file %s' % (gid, filename))
            groupname = 'unknown'
        return groupname

    def _get_group_windows(self, filename):
        return ""

    def format_timestamp(self, timestamp):
        d = datetime.datetime.fromtimestamp(timestamp)
        return d.strftime(r'%Y-%m-%d %H:%M')

    """ Like dir_scan_host, but in the connected Android device """

    def dir_scan_device(self, directory):
        output = []

        entries = self.aafm.get_device_file_list()

        dirs = []
        files = []

        for filename, entry in entries.iteritems():
            if entry['is_directory']:
                dirs.append(filename)
            else:
                files.append(filename)

        dirs.sort()
        files.sort()

        output.append({
            'directory': True,
            'name': '..',
            'size': 0,
            'timestamp': '',
            'permissions': '',
            'owner': '',
            'group': ''
        })

        for d in dirs:
            output.append({
                'directory':
                True,
                'name':
                d,
                'size':
                0,
                'timestamp':
                self.format_timestamp(entries[d]['timestamp']),
                'permissions':
                entries[d]['permissions'],
                'owner':
                entries[d]['owner'],
                'group':
                entries[d]['group']
            })

        for f in files:
            size = int(entries[f]['size'])
            output.append({
                'directory':
                False,
                'name':
                f,
                'size':
                size,
                'timestamp':
                self.format_timestamp(entries[f]['timestamp']),
                'permissions':
                entries[f]['permissions'],
                'owner':
                entries[f]['owner'],
                'group':
                entries[f]['group']
            })

        return output

    def on_host_tree_view_contextual_menu(self, widget, event):
        if event.button == 3:  # Right click
            builder = gtk.Builder()
            builder.add_from_file(
                os.path.join(self.basedir,
                             'data/glade/menu_contextual_host.xml'))
            menu = builder.get_object('menu')
            builder.connect_signals({
                'on_menuHostCopyToDevice_activate':
                self.on_host_copy_to_device_callback,
                'on_menuHostCreateDirectory_activate':
                self.on_host_create_directory_callback,
                'on_menuHostRefresh_activate':
                self.on_host_refresh_callback,
                'on_menuHostDeleteItem_activate':
                self.on_host_delete_item_callback,
                'on_menuHostRenameItem_activate':
                self.on_host_rename_item_callback
            })

            # Ensure only right options are available
            num_selected = len(self.get_host_selected_files())
            has_selection = num_selected > 0

            menuCopy = builder.get_object('menuHostCopyToDevice')
            menuCopy.set_sensitive(has_selection)

            menuDelete = builder.get_object('menuHostDeleteItem')
            menuDelete.set_sensitive(has_selection)

            menuRename = builder.get_object('menuHostRenameItem')
            menuRename.set_sensitive(num_selected == 1)

            menu.popup(None, None, None, event.button, event.time)
            return True

        # Not consuming the event
        return False

    # Copy to device
    def on_host_copy_to_device_callback(self, widget):
        for row in self.get_host_selected_files():
            src = os.path.join(self.host_cwd, row['filename'])
            self.add_to_queue(self.QUEUE_ACTION_COPY_TO_DEVICE, src,
                              self.device_cwd)
        self.process_queue()

    # Create host directory
    def on_host_create_directory_callback(self, widget):
        directory_name = self.dialog_get_directory_name()

        if directory_name is None:
            return

        full_path = os.path.join(self.host_cwd, directory_name)
        if not os.path.exists(full_path):
            os.mkdir(full_path)
            self.refresh_host_files()

    def on_host_refresh_callback(self, widget):
        self.refresh_host_files()

    def on_host_delete_item_callback(self, widget):
        selected = self.get_host_selected_files()
        items = []
        for item in selected:
            items.append(item['filename'])

        result = self.dialog_delete_confirmation(items)

        if result == gtk.RESPONSE_OK:
            for item in items:
                full_item_path = os.path.join(self.host_cwd, item)
                self.delete_item(full_item_path)
                self.refresh_host_files()

    def delete_item(self, path):
        if os.path.isfile(path):
            os.remove(path)
        else:
            shutil.rmtree(path)

    def on_host_rename_item_callback(self, widget):
        old_name = self.get_host_selected_files()[0]['filename']
        new_name = self.dialog_get_item_name(old_name)

        if new_name is None:
            return

        full_src_path = os.path.join(self.host_cwd, old_name)
        full_dst_path = os.path.join(self.host_cwd, new_name)

        shutil.move(full_src_path, full_dst_path)
        self.refresh_host_files()

    def on_device_tree_view_contextual_menu(self, widget, event):
        if event.button == 3:  # Right click
            builder = gtk.Builder()
            builder.add_from_file(
                os.path.join(self.basedir,
                             "data/glade/menu_contextual_device.xml"))
            menu = builder.get_object("menu")
            builder.connect_signals({
                'on_menuDeviceDeleteItem_activate':
                self.on_device_delete_item_callback,
                'on_menuDeviceCreateDirectory_activate':
                self.on_device_create_directory_callback,
                'on_menuDeviceRefresh_activate':
                self.on_device_refresh_callback,
                'on_menuDeviceCopyToComputer_activate':
                self.on_device_copy_to_computer_callback,
                'on_menuDeviceRenameItem_activate':
                self.on_device_rename_item_callback
            })

            # Ensure only right options are available
            num_selected = len(self.get_device_selected_files())
            has_selection = num_selected > 0
            menuDelete = builder.get_object('menuDeviceDeleteItem')
            menuDelete.set_sensitive(has_selection)

            menuCopy = builder.get_object('menuDeviceCopyToComputer')
            menuCopy.set_sensitive(has_selection)

            menuRename = builder.get_object('menuDeviceRenameItem')
            menuRename.set_sensitive(num_selected == 1)

            menu.popup(None, None, None, event.button, event.time)
            return True

        # don't consume the event, so we can still double click to navigate
        return False

    def on_device_delete_item_callback(self, widget):
        selected = self.get_device_selected_files()

        items = []

        for item in selected:
            items.append(item['filename'])

        result = self.dialog_delete_confirmation(items)

        if result == gtk.RESPONSE_OK:
            for item in items:
                full_item_path = self.aafm.device_path_join(
                    self.device_cwd, item)
                self.aafm.device_delete_item(full_item_path)
                self.refresh_device_files()
        else:
            print('no no')

    def dialog_delete_confirmation(self, items):
        items.sort()
        joined = ', '.join(items)
        dialog = gtk.MessageDialog(
            parent=None,
            flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            type=gtk.MESSAGE_QUESTION,
            buttons=gtk.BUTTONS_OK_CANCEL,
            message_format="Are you sure you want to delete %d items?" %
            len(items))
        dialog.format_secondary_markup(
            '%s will be deleted. This action cannot be undone.' % joined)
        dialog.show_all()
        result = dialog.run()

        dialog.destroy()
        return result

    def on_device_create_directory_callback(self, widget):
        directory_name = self.dialog_get_directory_name()

        # dialog was cancelled
        if directory_name is None:
            return

        full_path = self.aafm.device_path_join(self.device_cwd, directory_name)
        self.aafm.device_make_directory(full_path)
        self.refresh_device_files()

    def dialog_get_directory_name(self):
        dialog = gtk.MessageDialog(
            None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, None)

        dialog.set_markup('Please enter new directory name:')

        entry = gtk.Entry()
        entry.connect('activate', self.dialog_response, dialog,
                      gtk.RESPONSE_OK)

        hbox = gtk.HBox()
        hbox.pack_start(gtk.Label('Name:'), False, 5, 5)
        hbox.pack_end(entry)

        dialog.vbox.pack_end(hbox, True, True, 0)
        dialog.show_all()

        result = dialog.run()

        text = entry.get_text()
        dialog.destroy()

        if result == gtk.RESPONSE_OK:
            return text
        else:
            return None

    def dialog_response(self, entry, dialog, response):
        dialog.response(response)

    def on_device_refresh_callback(self, widget):
        self.refresh_device_files()

    def on_device_copy_to_computer_callback(self, widget):
        selected = self.get_device_selected_files()
        task = self.copy_from_device_task(selected)
        gobject.idle_add(task.next)

    def copy_from_device_task(self, rows):
        completed = 0
        total = len(rows)

        self.update_progress()

        for row in rows:
            filename = row['filename']
            is_directory = row['is_directory']

            full_device_path = self.aafm.device_path_join(
                self.device_cwd, filename)
            full_host_path = self.host_cwd

            self.aafm.copy_to_host(full_device_path, full_host_path)
            completed = completed + 1
            self.refresh_host_files()
            self.update_progress(completed * 1.0 / total)

            yield True

        yield False

    def on_device_rename_item_callback(self, widget):
        old_name = self.get_device_selected_files()[0]['filename']
        new_name = self.dialog_get_item_name(old_name)

        if new_name is None:
            return

        full_src_path = self.aafm.device_path_join(self.device_cwd, old_name)
        full_dst_path = self.aafm.device_path_join(self.device_cwd, new_name)

        self.aafm.device_rename_item(full_src_path, full_dst_path)
        self.refresh_device_files()

    def dialog_get_item_name(self, old_name):
        dialog = gtk.MessageDialog(
            None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, None)

        dialog.set_markup('Please enter new name:')

        entry = gtk.Entry()
        entry.connect('activate', self.dialog_response, dialog,
                      gtk.RESPONSE_OK)
        entry.set_text(old_name)

        hbox = gtk.HBox()
        hbox.pack_start(gtk.Label('Name:'), False, 5, 5)
        hbox.pack_end(entry)

        dialog.vbox.pack_end(hbox, True, True, 0)
        dialog.show_all()

        result = dialog.run()
        text = entry.get_text()
        dialog.destroy()

        if result == gtk.RESPONSE_OK:
            return text
        else:
            return None

    def update_progress(self, value=None):
        if value is None:
            self.progress_bar.set_fraction(0)
            self.progress_bar.set_text("")
            self.progress_bar.pulse()
        else:
            self.progress_bar.set_fraction(value)

            self.progress_bar.set_text("%d%%" % (value * 100))

        if value >= 1:
            self.progress_bar.set_text("Done")
            self.progress_bar.set_fraction(0)

    def on_host_drag_data_get(self, widget, context, selection, target_type,
                              time):
        data = '\n'.join([
            'file://' +
            urllib.quote(os.path.join(self.host_cwd, item['filename']))
            for item in self.get_host_selected_files()
        ])

        selection.set(selection.target, 8, data)

    def on_host_drag_data_received(self, tree_view, context, x, y, selection,
                                   info, timestamp):
        data = selection.data
        type = selection.type
        drop_info = tree_view.get_dest_row_at_pos(x, y)
        destination = self.host_cwd

        if drop_info:
            model = tree_view.get_model()
            path, position = drop_info

            if position in [
                    gtk.TREE_VIEW_DROP_INTO_OR_BEFORE,
                    gtk.TREE_VIEW_DROP_INTO_OR_AFTER
            ]:
                iter = model.get_iter(path)
                is_directory = model.get_value(iter, 0)
                name = model.get_value(iter, 1)

                # If dropping over a folder, copy things to that folder
                if is_directory:
                    destination = os.path.join(self.host_cwd, name)

        for line in [line.strip() for line in data.split('\n')]:
            if line.startswith('file://'):
                source = urllib.unquote(line.replace('file://', '', 1))

                if type == 'DRAG_SELF':
                    self.add_to_queue(self.QUEUE_ACTION_MOVE_IN_HOST, source,
                                      destination)
                elif type == 'ADB_text':
                    self.add_to_queue(self.QUEUE_ACTION_COPY_FROM_DEVICE,
                                      source, destination)

        self.process_queue()

    def on_device_drag_begin(self, widget, context):

        context.source_window.property_change(self.XDS_ATOM, self.TEXT_ATOM, 8,
                                              gtk.gdk.PROP_MODE_REPLACE,
                                              self.XDS_FILENAME)

    def on_device_drag_data_get(self, widget, context, selection, target_type,
                                time):

        if selection.target == 'XdndDirectSave0':
            type, format, destination_file = context.source_window.property_get(
                self.XDS_ATOM, self.TEXT_ATOM)

            if destination_file.startswith('file://'):
                destination = os.path.dirname(
                    urllib.unquote(destination_file).replace('file://', '', 1))
                for item in self.get_device_selected_files():
                    self.add_to_queue(
                        self.QUEUE_ACTION_COPY_FROM_DEVICE,
                        self.aafm.device_path_join(self.device_cwd,
                                                   item['filename']),
                        destination)

                self.process_queue()
            else:
                print("ERROR: Destination doesn't start with file://?!!?")

        else:
            selection.set(
                selection.target, 8, '\n'.join([
                    'file://' + urllib.quote(
                        self.aafm.device_path_join(self.device_cwd,
                                                   item['filename']))
                    for item in self.get_device_selected_files()
                ]))

    def on_device_drag_data_received(self, tree_view, context, x, y, selection,
                                     info, timestamp):

        data = selection.data
        type = selection.type
        drop_info = tree_view.get_dest_row_at_pos(x, y)
        destination = self.device_cwd

        # When dropped over a row
        if drop_info:
            model = tree_view.get_model()
            path, position = drop_info

            if position in [
                    gtk.TREE_VIEW_DROP_INTO_OR_BEFORE,
                    gtk.TREE_VIEW_DROP_INTO_OR_AFTER
            ]:
                iter = model.get_iter(path)
                is_directory = model.get_value(iter, 0)
                name = model.get_value(iter, 1)

                # If dropping over a folder, copy things to that folder
                if is_directory:
                    destination = self.aafm.device_path_join(
                        self.device_cwd, name)

        if type == 'DRAG_SELF':
            if self.device_cwd != destination:
                for line in [line.strip() for line in data.split('\n')]:
                    if line.startswith('file://'):
                        source = urllib.unquote(line.replace('file://', '', 1))
                        if source != destination:
                            name = self.aafm.device_path_basename(source)
                            self.add_to_queue(self.QUEUE_ACTION_MOVE_IN_DEVICE,
                                              source,
                                              os.path.join(destination, name))
        else:
            # COPY stuff
            for line in [line.strip() for line in data.split('\n')]:
                if line.startswith('file://'):
                    source = urllib.unquote(line.replace('file://', '', 1))
                    self.add_to_queue(self.QUEUE_ACTION_COPY_TO_DEVICE, source,
                                      destination)

        self.process_queue()

    def add_to_queue(self, action, src_file, dst_path):
        self.queue.append([action, src_file, dst_path])

    def process_queue(self):
        task = self.process_queue_task()
        gobject.idle_add(task.next)

    def process_queue_task(self):
        completed = 0
        self.update_progress()

        while len(self.queue) > 0:
            item = self.queue.pop()
            action, src, dst = item

            if action == self.QUEUE_ACTION_COPY_TO_DEVICE:
                self.aafm.copy_to_device(src, dst)
                self.refresh_device_files()
            if action == self.QUEUE_ACTION_COPY_FROM_DEVICE:
                self.aafm.copy_to_host(src, dst)
                self.refresh_host_files()
            elif action == self.QUEUE_ACTION_MOVE_IN_DEVICE:
                self.aafm.device_rename_item(src, dst)
                self.refresh_device_files()
            elif action == self.QUEUE_ACTION_MOVE_IN_HOST:
                shutil.move(src, dst)
                self.refresh_host_files()

            completed = completed + 1
            total = len(self.queue) + 1
            self.update_progress(completed * 1.0 / total)

            yield True

        yield False

    def die_callback(self, widget, data=None):
        self.destroy(widget, data)

    def destroy(self, widget, data=None):
        gtk.main_quit()

    def main(self):
        gtk.main()
Ejemplo n.º 4
0
class Aafm_GUI:

	QUEUE_ACTION_COPY_TO_DEVICE = 'copy_to_device'
	QUEUE_ACTION_COPY_FROM_DEVICE = 'copy_from_device'
	QUEUE_ACTION_CALLABLE = 'callable'
	QUEUE_ACTION_MOVE_IN_DEVICE = 'move_in_device'
	QUEUE_ACTION_MOVE_IN_HOST = 'move_in_host'

	# These constants are for dragging files to Nautilus
	XDS_ATOM = gtk.gdk.atom_intern("XdndDirectSave0")
	TEXT_ATOM = gtk.gdk.atom_intern("text/plain")
	XDS_FILENAME = 'whatever.txt'

	def __init__(self):
		
		# The super core
		self.aafm = Aafm('adb', os.getcwd(), '/mnt/sdcard/')
		self.queue = []

		self.basedir = os.path.dirname(os.path.abspath(__file__))
		
		if os.name == 'nt':
			self.get_owner = self._get_owner_windows
			self.get_group = self._get_group_windows
		else:
			self.get_owner = self._get_owner
			self.get_group = self._get_group

		# Build main window from XML
		builder = gtk.Builder()
		builder.add_from_file(os.path.join(self.basedir, "data/glade/interface.xml"))
		builder.connect_signals({ "on_window_destroy" : gtk.main_quit })
		self.window = builder.get_object("window")

		imageDir = gtk.Image()
		imageDir.set_from_file(os.path.join(self.basedir, './data/icons/folder.png'))
		imageFile = gtk.Image()
		imageFile.set_from_file(os.path.join(self.basedir, './data/icons/file.png'))
		
		# Show hidden files and folders
		self.showHidden = False

		showHidden = builder.get_object('showHidden')
		showHidden.connect('toggled', self.on_toggle_hidden)

		# Refresh view
		refreshView = builder.get_object('refreshView')
		refreshView.connect('activate', self.refresh_all)

		itemQuit = builder.get_object('itemQuit')
		itemQuit.connect('activate', gtk.main_quit)

		self.menuDevices = builder.get_object('menuDevices')
		self.refresh_menu_devices()

		# Host and device TreeViews
		
		# HOST
		self.host_treeViewFile = TreeViewFile(imageDir.get_pixbuf(), imageFile.get_pixbuf())
		
		hostFrame = builder.get_object('frameHost')
		hostFrame.get_child().add(self.host_treeViewFile.get_view())
		
		hostTree = self.host_treeViewFile.get_tree()
		hostTree.connect('row-activated', self.host_navigate_callback)
		hostTree.connect('button_press_event', self.on_host_tree_view_contextual_menu)
	
		host_targets = [
			('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
			('ADB_text', 0, 1),
			('text/plain', 0, 2)
		]

		hostTree.enable_model_drag_dest(
			host_targets,
			gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
		)
		hostTree.connect('drag-data-received', self.on_host_drag_data_received)

		hostTree.enable_model_drag_source(
			gtk.gdk.BUTTON1_MASK,
			host_targets,
			gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
		)
		hostTree.connect('drag_data_get', self.on_host_drag_data_get)
		
		self.hostFrame = hostFrame
		self.hostName = socket.gethostname()


		# DEVICE
		self.device_treeViewFile = TreeViewFile(imageDir.get_pixbuf(), imageFile.get_pixbuf())
		
		deviceFrame = builder.get_object('frameDevice')
		deviceFrame.get_child().add(self.device_treeViewFile.get_view())

		deviceTree = self.device_treeViewFile.get_tree()
		deviceTree.connect('row-activated', self.device_navigate_callback)
		deviceTree.connect('button_press_event', self.on_device_tree_view_contextual_menu)

		device_targets = [
			('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
			('ADB_text', 0, 1),
			('XdndDirectSave0', 0, 2),
			('text/plain', 0, 3)
		]

		deviceTree.enable_model_drag_dest(
			device_targets,
			gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
		)
		deviceTree.connect('drag-data-received', self.on_device_drag_data_received)
		
		deviceTree.enable_model_drag_source(
			gtk.gdk.BUTTON1_MASK,
			device_targets,
			gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
		)
		deviceTree.connect('drag-data-get', self.on_device_drag_data_get)
		deviceTree.connect('drag-begin', self.on_device_drag_begin)
		
		self.deviceFrame = deviceFrame


		# Progress bar
		self.progress_bar = builder.get_object('progressBar')

		# Some more subtle details...
		self.window.set_title("Android ADB file manager")
		#self.adb = 'adb'
		self.host_cwd = os.getcwd()
		self.aafm.set_device_cwd('/mnt/sdcard/')

		self.refresh_all()

		# Make both panels equal in size (at least initially)
		panelsPaned = builder.get_object('panelsPaned')
		self.window.show_all()
		panelsPaned.set_position(panelsPaned.get_allocation().width / 2)

		# And we're done!
		self.window.show_all()

	def refresh_menu_devices(self, widget=None):
		before = self.aafm.get_device_serial()
		self.aafm.refresh_devices()
		selected = self.aafm.get_device_serial()
		if before != selected:
			self.aafm.set_device_cwd('/mnt/sdcard/')
			self.refresh_device_files()

		def on_menu_item_toggled(item, serial):
			if item.get_active():
				self.aafm.set_device_serial(serial)
				self.aafm.set_device_cwd('/mnt/sdcard/')
				self.refresh_device_files()

		menu = self.menuDevices
		submenu = gtk.Menu()
		group = None
		for serial, name in self.aafm.get_devices():
			item = gtk.RadioMenuItem(group, '%s (%s)' % (name, serial))
			if serial == selected:
				item.set_active(True)

			item.connect('toggled', on_menu_item_toggled, serial)
			if group is None:
				group = item
			submenu.append(item)
		if group is None:
			item = gtk.MenuItem('No devices found')
			item.set_sensitive(False)
			submenu.append(item)
		submenu.append(gtk.SeparatorMenuItem())
		item = gtk.MenuItem('Refresh device list')
		item.connect('activate', self.refresh_menu_devices)
		submenu.append(item)
		menu.set_submenu(submenu)
		menu.show_all()



	def host_navigate_callback(self, widget, path, view_column):
		
		row = path[0]
		model = widget.get_model()
		iter = model.get_iter(row)
		is_dir = model.get_value(iter, 0)
		name = model.get_value(iter, 1)

		if is_dir:
			self.host_cwd = os.path.normpath(os.path.join(self.host_cwd, name))
			self.aafm.set_host_cwd(self.host_cwd)
			self.refresh_host_files()


	def device_navigate_callback(self, widget, path, view_column):

		row = path[0]
		model = widget.get_model()
		iter = model.get_iter(row)
		is_dir = model.get_value(iter, 0)
		name = model.get_value(iter, 1)

		if is_dir:
			self.aafm.set_device_cwd(self.aafm.device_path_normpath(self.aafm.device_path_join(self.aafm.device_cwd, name)))
			self.refresh_device_files()

	def refresh_all(self, widget=None):
		self.refresh_host_files()
		self.refresh_device_files()
	
	def refresh_host_files(self):
		self.host_treeViewFile.load_data(self.dir_scan_host(self.host_cwd))
		self.hostFrame.set_label('%s:%s' % (self.hostName, self.host_cwd))


	def refresh_device_files(self):
		self.device_treeViewFile.load_data(self.dir_scan_device(self.aafm.device_cwd))
		self.deviceFrame.set_label('%s:%s (%s free)' % ('device', self.aafm.device_cwd, self.aafm.get_free_space()))


	def get_treeviewfile_selected(self, treeviewfile):
		values = []
		model, rows = treeviewfile.get_tree().get_selection().get_selected_rows()

		for row in rows:
			iter = model.get_iter(row)
			filename = model.get_value(iter, 1)
			is_directory = model.get_value(iter, 0)
			values.append({'filename': filename, 'is_directory': is_directory})

		return values


	def get_host_selected_files(self):
		return self.get_treeviewfile_selected(self.host_treeViewFile)

	def get_device_selected_files(self):
		return self.get_treeviewfile_selected(self.device_treeViewFile)


	""" Walks through a directory and return the data in a tree-style list 
		that can be used by the TreeViewFile """
	def dir_scan_host(self, directory):
		output = []

		root, dirs, files = next(os.walk(directory))

		if not self.showHidden:
			files = [f for f in files if not f[0] == '.']
			dirs = [d for d in dirs if not d[0] == '.']

		dirs.sort()
		files.sort()

		output.append({'directory': True, 'name': '..', 'size': 0, 'timestamp': '',
				'permissions': '',
				'owner': '',
				'group': ''})

		for d in dirs:
			path = os.path.join(directory, d)
			output.append({
				'directory': True,
				'name': d,
				'size': 0,
				'timestamp': self.format_timestamp(os.path.getmtime(path)),
				'permissions': self.get_permissions(path),
				'owner': self.get_owner(path),
				'group': self.get_group(path)
			})

		for f in files:
			path = os.path.join(directory, f)

			try:
				size = os.path.getsize(path)
				output.append({
					'directory': False,
					'name': f,
					'size': size,
					'timestamp': self.format_timestamp(os.path.getmtime(path)),
					'permissions': self.get_permissions(path),
					'owner': self.get_owner(path),
					'group': self.get_group(path)
				})
			except OSError:
				pass

		return output

	""" The following three methods are probably NOT the best way of doing things.
	At least according to all the warnings that say os.stat is very costly
	and should be cached."""
	def get_permissions(self, filename):
		st = os.stat(filename)
		mode = st.st_mode
		permissions = ''

		bits = [ 
			stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR,
			stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP,
			stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH
		]

		attrs = ['r', 'w', 'x']

		for i in range(0, len(bits)):
			bit = bits[i]
			attr = attrs[i % len(attrs)]

			if bit & mode:
				permissions += attr
			else:
				permissions += '-'

		return permissions

	def _get_owner(self, filename):
		st = os.stat(filename)
		uid = st.st_uid
		try:
			user = pwd.getpwuid(uid)[0]
		except KeyError:
			print ('unknown uid %d for file %s' % (uid, filename))
			user = '******'
		return user
		
	def _get_owner_windows(self, filename):
		sd = win32security.GetFileSecurity(filename, win32security.OWNER_SECURITY_INFORMATION)
		owner_sid = sd.GetSecurityDescriptorOwner()
		name, domain, type = win32security.LookupAccountSid(None, owner_sid)
		return name

	def _get_group(self, filename):
		st = os.stat(filename)
		gid = st.st_gid
		try:
			groupname = grp.getgrgid(gid)[0]
		except KeyError:
			print ('unknown gid %d for file %s' % (gid, filename))
			groupname = 'unknown'
		return groupname
	
	def _get_group_windows(self, filename):
		return ""


	def format_timestamp(self, timestamp):
		d = datetime.datetime.fromtimestamp(timestamp)
		return d.strftime(r'%Y-%m-%d %H:%M')

	""" Like dir_scan_host, but in the connected Android device """
	def dir_scan_device(self, directory):
		output = []
		
		entries = self.aafm.get_device_file_list()

		dirs = []
		files = []

		for filename, entry in entries.iteritems():
			if entry['is_directory']:
				dirs.append(filename)
			else:
				files.append(filename)

		if not self.showHidden:
			files = [f for f in files if not f[0] == '.']
			dirs = [d for d in dirs if not d[0] == '.']

		dirs.sort()
		files.sort()

		output.append({'directory': True, 'name': '..', 'size': 0, 'timestamp': '', 'permissions': '', 'owner': '', 'group': ''})

		for d in dirs:
			output.append({
				'directory': True,
				'name': d,
				'size': 0,
				'timestamp': self.format_timestamp(entries[d]['timestamp']), 
				'permissions': entries[d]['permissions'],
				'owner': entries[d]['owner'],
				'group': entries[d]['group']
			})

		for f in files:
			size = int(entries[f]['size'])
			output.append({
				'directory': False,
				'name': f,
				'size': size,
				'timestamp': self.format_timestamp(entries[f]['timestamp']),
				'permissions': entries[f]['permissions'],
				'owner': entries[f]['owner'],
				'group': entries[f]['group']
			})

		return output


	def on_toggle_hidden(self, widget):
		self.showHidden = widget.get_active()

		self.refresh_all()

	def on_host_tree_view_contextual_menu(self, widget, event):
		if event.button == 3: # Right click
			builder = gtk.Builder()
			builder.add_from_file(os.path.join(self.basedir, 'data/glade/menu_contextual_host.xml'))
			menu = builder.get_object('menu')
			builder.connect_signals({
				'on_menuHostCopyToDevice_activate': self.on_host_copy_to_device_callback,
				'on_menuHostCreateDirectory_activate': self.on_host_create_directory_callback,
				'on_menuHostRefresh_activate': self.on_host_refresh_callback,
				'on_menuHostDeleteItem_activate': self.on_host_delete_item_callback,
				'on_menuHostRenameItem_activate': self.on_host_rename_item_callback
			})

			# Ensure only right options are available
			num_selected = len(self.get_host_selected_files())
			has_selection = num_selected > 0

			menuCopy = builder.get_object('menuHostCopyToDevice')
			menuCopy.set_sensitive(has_selection)

			menuDelete = builder.get_object('menuHostDeleteItem')
			menuDelete.set_sensitive(has_selection)

			menuRename = builder.get_object('menuHostRenameItem')
			menuRename.set_sensitive(num_selected == 1)	

			menu.popup(None, None, None, event.button, event.time)
			return True
		
		# Not consuming the event
		return False

	# Copy to device
	def on_host_copy_to_device_callback(self, widget):
		for row in self.get_host_selected_files():
			src = os.path.join(self.host_cwd, row['filename'])
			self.add_to_queue(self.QUEUE_ACTION_COPY_TO_DEVICE, src, self.aafm.device_cwd)
		self.process_queue()

	
	# Create host directory
	def on_host_create_directory_callback(self, widget):
		directory_name = self.dialog_get_directory_name()

		if directory_name is None:
			return

		full_path = os.path.join(self.host_cwd, directory_name)
		if not os.path.exists(full_path):
			os.mkdir(full_path)
			self.refresh_host_files()


	def on_host_refresh_callback(self, widget):
		self.refresh_host_files()


	def on_host_delete_item_callback(self, widget):
		selected = self.get_host_selected_files()
		items = []
		for item in selected:
			items.append(item['filename'])
			
		result = self.dialog_delete_confirmation(items)

		if result == gtk.RESPONSE_OK:
			for item in items:
				full_item_path = os.path.join(self.host_cwd, item)
				self.delete_item(full_item_path)
				self.refresh_host_files()

	def delete_item(self, path):
		if os.path.isfile(path):
			os.remove(path)
		else:
			shutil.rmtree(path)

	def on_host_rename_item_callback(self, widget):
		old_name = self.get_host_selected_files()[0]['filename']
		new_name = self.dialog_get_item_name(old_name)

		if new_name is None:
			return

		full_src_path = os.path.join(self.host_cwd, old_name)
		full_dst_path = os.path.join(self.host_cwd, new_name)

		shutil.move(full_src_path, full_dst_path)
		self.refresh_host_files()

	def on_device_tree_view_contextual_menu(self, widget, event):
		if event.button == 3: # Right click
			builder = gtk.Builder()
			builder.add_from_file(os.path.join(self.basedir, "data/glade/menu_contextual_device.xml"))
			menu = builder.get_object("menu")
			builder.connect_signals({
				'on_menuDeviceDeleteItem_activate': self.on_device_delete_item_callback,
				'on_menuDeviceCreateDirectory_activate': self.on_device_create_directory_callback,
				'on_menuDeviceRefresh_activate': self.on_device_refresh_callback,
				'on_menuDeviceCopyToComputer_activate': self.on_device_copy_to_computer_callback,
				'on_menuDeviceRenameItem_activate': self.on_device_rename_item_callback
			})

			# Ensure only right options are available
			num_selected = len(self.get_device_selected_files())
			has_selection = num_selected > 0
			menuDelete = builder.get_object('menuDeviceDeleteItem')
			menuDelete.set_sensitive(has_selection)
			
			menuCopy = builder.get_object('menuDeviceCopyToComputer')
			menuCopy.set_sensitive(has_selection)

			menuRename = builder.get_object('menuDeviceRenameItem')
			menuRename.set_sensitive(num_selected == 1)

			menu.popup(None, None, None, event.button, event.time)
			return True
		
		# don't consume the event, so we can still double click to navigate
		return False

	def on_device_delete_item_callback(self, widget):
		selected = self.get_device_selected_files()

		items = []

		for item in selected:
			items.append(item['filename'])

		result = self.dialog_delete_confirmation(items)

		if result == gtk.RESPONSE_OK:
			for item in items:
				full_item_path = self.aafm.device_path_join(self.aafm.device_cwd, item)
				for func, args in self.aafm.device_delete_item(full_item_path):
					self.add_to_queue(self.QUEUE_ACTION_CALLABLE, func, args)
				self.add_to_queue(self.QUEUE_ACTION_CALLABLE, self.refresh_device_files, ())
				self.process_queue()
		else:
			print('no no')


	def dialog_delete_confirmation(self, items):
		items.sort()
		joined = ', '.join(items)
		dialog = gtk.MessageDialog(
			parent = None,
			flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
			type = gtk.MESSAGE_QUESTION,
			buttons = gtk.BUTTONS_OK_CANCEL,
			message_format = "Are you sure you want to delete %d items?" % len(items)
		)
		dialog.format_secondary_markup('%s will be deleted. This action cannot be undone.' % joined)
		dialog.show_all()
		result = dialog.run()
		
		dialog.destroy()
		return result

	def on_device_create_directory_callback(self, widget):
		directory_name = self.dialog_get_directory_name()

		# dialog was cancelled
		if directory_name is None:
			return

		full_path = self.aafm.device_path_join(self.aafm.device_cwd, directory_name)
		self.aafm.device_make_directory(full_path)
		self.refresh_device_files()


	def dialog_get_directory_name(self):
		dialog = gtk.MessageDialog(
			None,
			gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
			gtk.MESSAGE_QUESTION,
			gtk.BUTTONS_OK_CANCEL,
			None)

		dialog.set_markup('Please enter new directory name:')

		entry = gtk.Entry()
		entry.connect('activate', self.dialog_response, dialog, gtk.RESPONSE_OK)

		hbox = gtk.HBox()
		hbox.pack_start(gtk.Label('Name:'), False, 5, 5)
		hbox.pack_end(entry)

		dialog.vbox.pack_end(hbox, True, True, 0)
		dialog.show_all()

		result = dialog.run()

		text = entry.get_text()
		dialog.destroy()

		if result == gtk.RESPONSE_OK:
			return text
		else:
			return None


	def dialog_response(self, entry, dialog, response):
		dialog.response(response)


	def on_device_refresh_callback(self, widget):
		self.refresh_device_files()


	def on_device_copy_to_computer_callback(self, widget):
		selected = self.get_device_selected_files()
		task = self.copy_from_device_task(selected)
		gobject.idle_add(task.next)


	def copy_from_device_task(self, rows):
		for row in rows:
			filename = row['filename']

			full_device_path = self.aafm.device_path_join(self.aafm.device_cwd, filename)
			full_host_path = self.host_cwd

			self.add_to_queue(self.QUEUE_ACTION_COPY_FROM_DEVICE, full_device_path, full_host_path)

		self.process_queue()
		yield False

	def on_device_rename_item_callback(self, widget):
		old_name = self.get_device_selected_files()[0]['filename']
		new_name = self.dialog_get_item_name(old_name)

		if new_name is None:
			return

		full_src_path = self.aafm.device_path_join(self.aafm.device_cwd, old_name)
		full_dst_path = self.aafm.device_path_join(self.aafm.device_cwd, new_name)

		self.aafm.device_rename_item(full_src_path, full_dst_path)
		self.refresh_device_files()
	
	def dialog_get_item_name(self, old_name):
		dialog = gtk.MessageDialog(
			None,
			gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
			gtk.MESSAGE_QUESTION,
			gtk.BUTTONS_OK_CANCEL,
			None)

		dialog.set_markup('Please enter new name:')

		entry = gtk.Entry()
		entry.connect('activate', self.dialog_response, dialog, gtk.RESPONSE_OK)
		entry.set_text(old_name)

		hbox = gtk.HBox()
		hbox.pack_start(gtk.Label('Name:'), False, 5, 5)
		hbox.pack_end(entry)

		dialog.vbox.pack_end(hbox, True, True, 0)
		dialog.show_all()

		result = dialog.run()
		text = entry.get_text()
		dialog.destroy()
		
		if result == gtk.RESPONSE_OK:
			return text
		else:
			return None


	def update_progress(self, value = None):
		if value is None:
			self.progress_bar.set_fraction(0)
			self.progress_bar.set_text("Ready")
			self.progress_bar.pulse()
		else:
			self.progress_bar.set_fraction(value)

			self.progress_bar.set_text("%d%%" % (value * 100))

		if value >= 1:
			self.progress_bar.set_text("Done")
			self.progress_bar.set_fraction(0)

		# Make sure the GUI has some cycles for processing events
		while gtk.events_pending():
			gtk.main_iteration(False)


	def on_host_drag_data_get(self, widget, context, selection, target_type, time):
		data = '\n'.join(['file://' + urllib.quote(os.path.join(self.host_cwd, item['filename'])) for item in self.get_host_selected_files()])
		
		selection.set(selection.target, 8, data)

	
	def on_host_drag_data_received(self, tree_view, context, x, y, selection, info, timestamp):
		data = selection.data
		type = selection.type
		drop_info = tree_view.get_dest_row_at_pos(x, y)
		destination = self.host_cwd
		
		if drop_info:
			model = tree_view.get_model()
			path, position = drop_info
			
			if position in [ gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_AFTER ]:
				iter = model.get_iter(path)
				is_directory = model.get_value(iter, 0)
				name = model.get_value(iter, 1)

				# If dropping over a folder, copy things to that folder
				if is_directory:
					destination = os.path.join(self.host_cwd, name)

		for line in [line.strip() for line in data.split('\n')]:
			if line.startswith('file://'):
				source = urllib.unquote(line.replace('file://', '', 1))

				if type == 'DRAG_SELF':
					self.add_to_queue(self.QUEUE_ACTION_MOVE_IN_HOST, source, destination)
				elif type == 'ADB_text':
					self.add_to_queue(self.QUEUE_ACTION_COPY_FROM_DEVICE, source, destination)

		self.process_queue()



	def on_device_drag_begin(self, widget, context):
		
		context.source_window.property_change(self.XDS_ATOM, self.TEXT_ATOM, 8, gtk.gdk.PROP_MODE_REPLACE, self.XDS_FILENAME)
	

	def on_device_drag_data_get(self, widget, context, selection, target_type, time):
		
		if selection.target == 'XdndDirectSave0':
			type, format, destination_file = context.source_window.property_get(self.XDS_ATOM, self.TEXT_ATOM)

			if destination_file.startswith('file://'):
				destination = os.path.dirname(urllib.unquote(destination_file).replace('file://', '', 1))
				for item in self.get_device_selected_files():
					self.add_to_queue(self.QUEUE_ACTION_COPY_FROM_DEVICE, self.aafm.device_path_join(self.aafm.device_cwd, item['filename']), destination)

				self.process_queue()
			else:
				print("ERROR: Destination doesn't start with file://?!!?")


		else:
			selection.set(selection.target, 8, '\n'.join(['file://' + urllib.quote(self.aafm.device_path_join(self.aafm.device_cwd, item['filename'])) for item in self.get_device_selected_files()]))
	

	def on_device_drag_data_received(self, tree_view, context, x, y, selection, info, timestamp):

		data = selection.data
		type = selection.type
		drop_info = tree_view.get_dest_row_at_pos(x, y)
		destination = self.aafm.device_cwd
		
		# When dropped over a row
		if drop_info:
			model = tree_view.get_model()
			path, position = drop_info
			
			if position in [ gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_AFTER ]:
				iter = model.get_iter(path)
				is_directory = model.get_value(iter, 0)
				name = model.get_value(iter, 1)

				# If dropping over a folder, copy things to that folder
				if is_directory:
					destination = self.aafm.device_path_join(self.aafm.device_cwd, name)

		if type == 'DRAG_SELF':
			if self.aafm.device_cwd != destination:
				for line in [line.strip() for line in data.split('\n')]:
					if line.startswith('file://'):
						source = urllib.unquote(line.replace('file://', '', 1))
						if source != destination:
							name = self.aafm.device_path_basename(source)
							self.add_to_queue(self.QUEUE_ACTION_MOVE_IN_DEVICE, source, os.path.join(destination, name))
		else:
			# COPY stuff
			for line in [line.strip() for line in data.split('\n')]:
				if line.startswith('file://'):
					source = urllib.unquote(line.replace('file://', '', 1))
					self.add_to_queue(self.QUEUE_ACTION_COPY_TO_DEVICE, source, destination)
		
		self.process_queue()


	def add_to_queue(self, action, src_file, dst_path):
		self.queue.append([action, src_file, dst_path])
	

	def process_queue(self):
		task = self.process_queue_task()
		gobject.idle_add(task.next)
	
	def process_queue_task(self):
		completed = 0
		self.update_progress()

		while len(self.queue) > 0:
			item = self.queue.pop(0)
			action, src, dst = item

			if action == self.QUEUE_ACTION_COPY_TO_DEVICE:
				for func, args in self.aafm.generate_copy_to_device_tasks(src, dst):
					self.add_to_queue(self.QUEUE_ACTION_CALLABLE, func, args)
				self.add_to_queue(self.QUEUE_ACTION_CALLABLE, self.refresh_device_files, ())
			elif action == self.QUEUE_ACTION_COPY_FROM_DEVICE:
				for func, args in self.aafm.generate_copy_to_host_tasks(src, dst):
					self.add_to_queue(self.QUEUE_ACTION_CALLABLE, func, args)
				self.add_to_queue(self.QUEUE_ACTION_CALLABLE, self.refresh_host_files, ())
			elif action == self.QUEUE_ACTION_CALLABLE:
				src(*dst)
			elif action == self.QUEUE_ACTION_MOVE_IN_DEVICE:
				self.aafm.device_rename_item(src, dst)
				self.refresh_device_files()
			elif action == self.QUEUE_ACTION_MOVE_IN_HOST:
				shutil.move(src, dst)
				self.refresh_host_files()

			completed += 1
			self.update_progress(float(completed) / float(completed + len(self.queue)))

			yield True

		yield False



	def die_callback(self, widget, data=None):
		self.destroy(widget, data)


	def destroy(self, widget, data=None):
		gtk.main_quit()


	def main(self):
		gtk.main()
Ejemplo n.º 5
0
    def __init__(self):

        self.done = True
        self.devices_list = []
        self.showing_notice = False
        self.progress_value = None

        # Read settings
        self.config = ConfigParser.SafeConfigParser({
            'lastdir_host': '',
            'lastdir_device': '',
            'startdir_host': 'last',
            'startdir_host_path': '',
            'startdir_device': 'last',
            'startdir_device_path': '',
            'show_hidden': 'no',
            'show_modified': 'yes',
            'show_permissions': 'no',
            'show_owner': 'no',
            'show_group': 'no',
            'last_serial': '',
            'last_ip': ''
        })
        self.config_file_loc = ""
        self.config_file_loc_default = os.path.join(os.path.expanduser("~"),
                                                    ".aafm")
        for file_loc in os.curdir, os.path.expanduser("~"), os.environ.get(
                "AAFM_CONF"):
            try:
                if file_loc is not None:
                    with open(os.path.join(file_loc, ".aafm")) as source:
                        self.config.readfp(source)
                        self.config_file_loc = file_loc
                        break
            except IOError:
                pass

        # Test for the aafm section and add it if it's missing
        try:
            self.config.get("aafm", "startdir_host")
        except ConfigParser.NoSectionError:
            self.config.add_section("aafm")

        # Store config variables
        self.startDirHost = self.config.get("aafm", "startdir_host")
        self.startDirHostPath = self.config.get("aafm", "startdir_host_path")
        self.startDirDevice = self.config.get("aafm", "startdir_device")
        self.startDirDevicePath = self.config.get("aafm",
                                                  "startdir_device_path")
        self.showHidden = (self.config.get("aafm", "show_hidden") == "yes")
        self.showModified = (self.config.get("aafm", "show_modified") == "yes")
        self.showPermissions = (self.config.get("aafm",
                                                "show_permissions") == "yes")
        self.showOwner = (self.config.get("aafm", "show_owner") == "yes")
        self.showGroup = (self.config.get("aafm", "show_group") == "yes")
        self.lastDeviceSerial = self.config.get("aafm", "last_serial")
        self.lastDeviceIP = self.config.get("aafm", "last_ip")

        if self.startDirHost == 'last':
            self.host_cwd = self.config.get("aafm", "lastdir_host")
        else:
            self.host_cwd = self.startDirHostPath

        if not os.path.isdir(self.host_cwd):
            self.host_cwd = os.path.expanduser("~")

        if self.startDirDevice == 'last':
            self.device_cwd_default = self.config.get("aafm", "lastdir_device")
        else:
            self.device_cwd_default = self.startDirDevicePath

        if self.device_cwd_default == '':
            self.device_cwd_default = '/mnt/sdcard'

        self.device_cwd = self.device_cwd_default

        # The super core
        self.aafm = Aafm('adb', self.host_cwd, self.device_cwd, self)
        self.queue = []

        self.basedir = os.path.dirname(os.path.abspath(__file__))

        if os.name == 'nt':
            self.get_owner = self._get_owner_windows
            self.get_group = self._get_group_windows
        else:
            self.get_owner = self._get_owner
            self.get_group = self._get_group

        # Build main window from XML
        builder = gtk.Builder()
        builder.add_from_file(
            os.path.join(self.basedir, "data/glade/interface.xml"))
        builder.connect_signals({"on_window_destroy": self.destroy})
        self.window = builder.get_object("window")
        vbox1 = builder.get_object("vbox1")

        # Set preferences window var
        self.window_prefs = None

        # Build menu from XML
        uimanager = gtk.UIManager()
        accelgroup = uimanager.get_accel_group()
        self.window.add_accel_group(accelgroup)

        actiongroup = gtk.ActionGroup('Main')
        actiongroup.add_actions([
            ('Preferences', gtk.STOCK_PREFERENCES, '_Preferences', None,
             'Preferences', self.open_prefs),
            ('Quit', gtk.STOCK_QUIT, '_Quit', None, 'Quit aafm', self.destroy),
            ('File', None, '_File')
        ])
        uimanager.insert_action_group(actiongroup, 0)

        uimanager.add_ui_from_file(
            os.path.join(self.basedir, "data/glade/menu.xml"))
        menubar = uimanager.get_widget('/MenuBar')

        vbox1.pack_start(menubar, False, False, 0)
        vbox1.reorder_child(menubar, 0)

        imageDir = gtk.Image()
        imageDir.set_from_file(
            os.path.join(self.basedir, "data/icons/folder.png"))
        imageFile = gtk.Image()
        imageFile.set_from_file(
            os.path.join(self.basedir, "data/icons/file.png"))

        # Show hidden files and folders
        showHidden = builder.get_object('showHidden')
        showHidden.set_active(self.showHidden)
        showHidden.connect('toggled', self.on_toggle_hidden)

        # Progress bar
        self.progress_bar = builder.get_object('progressBar')

        # Host and device TreeViews

        # HOST
        self.host_treeViewFile = TreeViewFile(imageDir.get_pixbuf(),
                                              imageFile.get_pixbuf(),
                                              self.showModified,
                                              self.showPermissions,
                                              self.showOwner, self.showGroup)

        hostFrame = builder.get_object('frameHost')
        hostFrame.get_child().add(self.host_treeViewFile.get_view())

        hostTree = self.host_treeViewFile.get_tree()
        hostTree.connect('row-activated', self.host_navigate_callback)
        hostTree.connect('button_press_event',
                         self.on_host_tree_view_contextual_menu)

        host_targets = [('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
                        ('ADB_text', 0, 1), ('text/plain', 0, 2)]

        hostTree.enable_model_drag_dest(
            host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        hostTree.connect('drag-data-received', self.on_host_drag_data_received)

        hostTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK, host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        hostTree.connect('drag_data_get', self.on_host_drag_data_get)

        self.hostFrame = hostFrame
        self.hostName = socket.gethostname()

        # DEVICE
        self.device_treeViewFile = TreeViewFile(imageDir.get_pixbuf(),
                                                imageFile.get_pixbuf(),
                                                self.showModified,
                                                self.showPermissions,
                                                self.showOwner, self.showGroup)

        deviceFrame = builder.get_object('frameDevice')
        deviceFrame.get_child().add(self.device_treeViewFile.get_view())

        deviceTree = self.device_treeViewFile.get_tree()
        deviceTree.connect('row-activated', self.device_navigate_callback)
        deviceTree.connect('button_press_event',
                           self.on_device_tree_view_contextual_menu)

        device_targets = [('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
                          ('ADB_text', 0, 1), ('XdndDirectSave0', 0, 2),
                          ('text/plain', 0, 3)]

        deviceTree.enable_model_drag_dest(
            device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        deviceTree.connect('drag-data-received',
                           self.on_device_drag_data_received)

        deviceTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK, device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        deviceTree.connect('drag-data-get', self.on_device_drag_data_get)
        deviceTree.connect('drag-begin', self.on_device_drag_begin)

        self.deviceFrame = deviceFrame

        # Add device button
        addDevice = builder.get_object('addDevice')
        addDevice.connect('clicked', self.on_clicked_add_device)

        # Devices list combobox
        self.devicesList = builder.get_object('devicesList')
        self.devices_list_store = gtk.ListStore(gobject.TYPE_STRING)
        self.refresh_devices_list(True, self.lastDeviceSerial)
        self.devicesList.set_model(self.devices_list_store)

        cell = gtk.CellRendererText()
        self.devicesList.pack_start(cell, True)
        self.devicesList.add_attribute(cell, "text", 0)

        # if self.lastDeviceSerial in self.devices_list:
        #    self.aafm.set_device(self.lastDeviceSerial)
        #elif len(self.devices_list) > 0:
        #    self.aafm.set_device(self.aafm.get_device_serial(self.devices_list[0]))

        self.devicesList.connect('changed', self.on_change_device)

        # Devices list refresh button
        refreshDevicesList = builder.get_object('refreshDevicesList')
        refreshDevicesList.connect('clicked', self.on_clicked_refresh_devices)

        # Some more subtle details...
        try:
            self.window.set_icon_from_file(
                os.path.join(self.basedir, "data/icons/aafm.svg"))
        except:
            self.window.set_icon_from_file(
                os.path.join(self.basedir, "data/icons/aafm.png"))
            # self.adb = 'adb'

        self.refresh_host_files()

        # And we're done!
        self.window.show_all()
Ejemplo n.º 6
0
class Aafm_GUI:
    QUEUE_ACTION_COPY_TO_DEVICE = 'copy_to_device'
    QUEUE_ACTION_COPY_FROM_DEVICE = 'copy_from_device'
    QUEUE_ACTION_MOVE_IN_DEVICE = 'move_in_device'
    QUEUE_ACTION_MOVE_IN_HOST = 'move_in_host'

    # These constants are for dragging files to Nautilus
    XDS_ATOM = gtk.gdk.atom_intern("XdndDirectSave0")
    TEXT_ATOM = gtk.gdk.atom_intern("text/plain")
    XDS_FILENAME = 'whatever.txt'

    def __init__(self):

        self.done = True
        self.devices_list = []
        self.showing_notice = False
        self.progress_value = None

        # Read settings
        self.config = ConfigParser.SafeConfigParser({
            'lastdir_host': '',
            'lastdir_device': '',
            'startdir_host': 'last',
            'startdir_host_path': '',
            'startdir_device': 'last',
            'startdir_device_path': '',
            'show_hidden': 'no',
            'show_modified': 'yes',
            'show_permissions': 'no',
            'show_owner': 'no',
            'show_group': 'no',
            'last_serial': '',
            'last_ip': ''
        })
        self.config_file_loc = ""
        self.config_file_loc_default = os.path.join(os.path.expanduser("~"),
                                                    ".aafm")
        for file_loc in os.curdir, os.path.expanduser("~"), os.environ.get(
                "AAFM_CONF"):
            try:
                if file_loc is not None:
                    with open(os.path.join(file_loc, ".aafm")) as source:
                        self.config.readfp(source)
                        self.config_file_loc = file_loc
                        break
            except IOError:
                pass

        # Test for the aafm section and add it if it's missing
        try:
            self.config.get("aafm", "startdir_host")
        except ConfigParser.NoSectionError:
            self.config.add_section("aafm")

        # Store config variables
        self.startDirHost = self.config.get("aafm", "startdir_host")
        self.startDirHostPath = self.config.get("aafm", "startdir_host_path")
        self.startDirDevice = self.config.get("aafm", "startdir_device")
        self.startDirDevicePath = self.config.get("aafm",
                                                  "startdir_device_path")
        self.showHidden = (self.config.get("aafm", "show_hidden") == "yes")
        self.showModified = (self.config.get("aafm", "show_modified") == "yes")
        self.showPermissions = (self.config.get("aafm",
                                                "show_permissions") == "yes")
        self.showOwner = (self.config.get("aafm", "show_owner") == "yes")
        self.showGroup = (self.config.get("aafm", "show_group") == "yes")
        self.lastDeviceSerial = self.config.get("aafm", "last_serial")
        self.lastDeviceIP = self.config.get("aafm", "last_ip")

        if self.startDirHost == 'last':
            self.host_cwd = self.config.get("aafm", "lastdir_host")
        else:
            self.host_cwd = self.startDirHostPath

        if not os.path.isdir(self.host_cwd):
            self.host_cwd = os.path.expanduser("~")

        if self.startDirDevice == 'last':
            self.device_cwd_default = self.config.get("aafm", "lastdir_device")
        else:
            self.device_cwd_default = self.startDirDevicePath

        if self.device_cwd_default == '':
            self.device_cwd_default = '/mnt/sdcard'

        self.device_cwd = self.device_cwd_default

        # The super core
        self.aafm = Aafm('adb', self.host_cwd, self.device_cwd, self)
        self.queue = []

        self.basedir = os.path.dirname(os.path.abspath(__file__))

        if os.name == 'nt':
            self.get_owner = self._get_owner_windows
            self.get_group = self._get_group_windows
        else:
            self.get_owner = self._get_owner
            self.get_group = self._get_group

        # Build main window from XML
        builder = gtk.Builder()
        builder.add_from_file(
            os.path.join(self.basedir, "data/glade/interface.xml"))
        builder.connect_signals({"on_window_destroy": self.destroy})
        self.window = builder.get_object("window")
        vbox1 = builder.get_object("vbox1")

        # Set preferences window var
        self.window_prefs = None

        # Build menu from XML
        uimanager = gtk.UIManager()
        accelgroup = uimanager.get_accel_group()
        self.window.add_accel_group(accelgroup)

        actiongroup = gtk.ActionGroup('Main')
        actiongroup.add_actions([
            ('Preferences', gtk.STOCK_PREFERENCES, '_Preferences', None,
             'Preferences', self.open_prefs),
            ('Quit', gtk.STOCK_QUIT, '_Quit', None, 'Quit aafm', self.destroy),
            ('File', None, '_File')
        ])
        uimanager.insert_action_group(actiongroup, 0)

        uimanager.add_ui_from_file(
            os.path.join(self.basedir, "data/glade/menu.xml"))
        menubar = uimanager.get_widget('/MenuBar')

        vbox1.pack_start(menubar, False, False, 0)
        vbox1.reorder_child(menubar, 0)

        imageDir = gtk.Image()
        imageDir.set_from_file(
            os.path.join(self.basedir, "data/icons/folder.png"))
        imageFile = gtk.Image()
        imageFile.set_from_file(
            os.path.join(self.basedir, "data/icons/file.png"))

        # Show hidden files and folders
        showHidden = builder.get_object('showHidden')
        showHidden.set_active(self.showHidden)
        showHidden.connect('toggled', self.on_toggle_hidden)

        # Progress bar
        self.progress_bar = builder.get_object('progressBar')

        # Host and device TreeViews

        # HOST
        self.host_treeViewFile = TreeViewFile(imageDir.get_pixbuf(),
                                              imageFile.get_pixbuf(),
                                              self.showModified,
                                              self.showPermissions,
                                              self.showOwner, self.showGroup)

        hostFrame = builder.get_object('frameHost')
        hostFrame.get_child().add(self.host_treeViewFile.get_view())

        hostTree = self.host_treeViewFile.get_tree()
        hostTree.connect('row-activated', self.host_navigate_callback)
        hostTree.connect('button_press_event',
                         self.on_host_tree_view_contextual_menu)

        host_targets = [('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
                        ('ADB_text', 0, 1), ('text/plain', 0, 2)]

        hostTree.enable_model_drag_dest(
            host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        hostTree.connect('drag-data-received', self.on_host_drag_data_received)

        hostTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK, host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        hostTree.connect('drag_data_get', self.on_host_drag_data_get)

        self.hostFrame = hostFrame
        self.hostName = socket.gethostname()

        # DEVICE
        self.device_treeViewFile = TreeViewFile(imageDir.get_pixbuf(),
                                                imageFile.get_pixbuf(),
                                                self.showModified,
                                                self.showPermissions,
                                                self.showOwner, self.showGroup)

        deviceFrame = builder.get_object('frameDevice')
        deviceFrame.get_child().add(self.device_treeViewFile.get_view())

        deviceTree = self.device_treeViewFile.get_tree()
        deviceTree.connect('row-activated', self.device_navigate_callback)
        deviceTree.connect('button_press_event',
                           self.on_device_tree_view_contextual_menu)

        device_targets = [('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
                          ('ADB_text', 0, 1), ('XdndDirectSave0', 0, 2),
                          ('text/plain', 0, 3)]

        deviceTree.enable_model_drag_dest(
            device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        deviceTree.connect('drag-data-received',
                           self.on_device_drag_data_received)

        deviceTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK, device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        deviceTree.connect('drag-data-get', self.on_device_drag_data_get)
        deviceTree.connect('drag-begin', self.on_device_drag_begin)

        self.deviceFrame = deviceFrame

        # Add device button
        addDevice = builder.get_object('addDevice')
        addDevice.connect('clicked', self.on_clicked_add_device)

        # Devices list combobox
        self.devicesList = builder.get_object('devicesList')
        self.devices_list_store = gtk.ListStore(gobject.TYPE_STRING)
        self.refresh_devices_list(True, self.lastDeviceSerial)
        self.devicesList.set_model(self.devices_list_store)

        cell = gtk.CellRendererText()
        self.devicesList.pack_start(cell, True)
        self.devicesList.add_attribute(cell, "text", 0)

        # if self.lastDeviceSerial in self.devices_list:
        #    self.aafm.set_device(self.lastDeviceSerial)
        #elif len(self.devices_list) > 0:
        #    self.aafm.set_device(self.aafm.get_device_serial(self.devices_list[0]))

        self.devicesList.connect('changed', self.on_change_device)

        # Devices list refresh button
        refreshDevicesList = builder.get_object('refreshDevicesList')
        refreshDevicesList.connect('clicked', self.on_clicked_refresh_devices)

        # Some more subtle details...
        try:
            self.window.set_icon_from_file(
                os.path.join(self.basedir, "data/icons/aafm.svg"))
        except:
            self.window.set_icon_from_file(
                os.path.join(self.basedir, "data/icons/aafm.png"))
            # self.adb = 'adb'

        self.refresh_host_files()

        # And we're done!
        self.window.show_all()

    def host_navigate_callback(self, widget, path, view_column):

        row = path[0]
        model = widget.get_model()
        iter = model.get_iter(row)
        is_dir = model.get_value(iter, 0)
        name = model.get_value(iter, 1)

        if is_dir:
            self.host_cwd = os.path.normpath(os.path.join(self.host_cwd, name))
            self.aafm.set_host_cwd(self.host_cwd)
            self.refresh_host_files()

    def device_navigate_callback(self, widget, path, view_column):

        row = path[0]
        model = widget.get_model()
        iter = model.get_iter(row)
        is_dir = model.get_value(iter, 0)
        name = model.get_value(iter, 1)

        if is_dir:
            self.device_cwd = self.aafm.device_path_normpath(
                self.aafm.device_path_join(self.device_cwd, name))
            self.aafm.set_device_cwd(self.device_cwd)
            self.refresh_device_files()

    def refresh_host_files(self):
        self.host_treeViewFile.load_data(self.dir_scan_host(self.host_cwd))
        self.hostFrame.set_label(self.host_cwd)

    def refresh_device_files(self):
        print 'Refreshing device files'
        if self.aafm.device != '':
            self.device_treeViewFile.load_data(
                self.dir_scan_device(self.device_cwd))
            self.deviceFrame.set_label(self.device_cwd)
        else:
            self.device_treeViewFile.clear_data()

    def get_treeviewfile_selected(self, treeviewfile):
        values = []
        model, rows = treeviewfile.get_tree().get_selection(
        ).get_selected_rows()

        for row in rows:
            iter = model.get_iter(row)
            filename = model.get_value(iter, 1)
            is_directory = model.get_value(iter, 0)
            values.append({'filename': filename, 'is_directory': is_directory})

        return values

    def get_host_selected_files(self):
        return self.get_treeviewfile_selected(self.host_treeViewFile)

    def get_device_selected_files(self):
        return self.get_treeviewfile_selected(self.device_treeViewFile)

    def human_readable_size(self, size):
        for x in ['B', 'K', 'M', 'G']:
            if size < 1024.0:
                return "%3.1f%s" % (size, x)
            size /= 1024.0
        return "%3.1f%s" % (size, 'T')

    """ Walks through a directory and return the data in a tree-style list
        that can be used by the TreeViewFile """

    def dir_scan_host(self, directory):
        output = []

        root, dirs, files = next(os.walk(directory))

        if not self.showHidden:
            files = [f for f in files if not f[0] == '.']
            dirs = [d for d in dirs if not d[0] == '.']

        dirs.sort()
        files.sort()

        output.append({
            'directory': True,
            'name': '..',
            'size': 0,
            'timestamp': '',
            'permissions': '',
            'owner': '',
            'group': ''
        })

        for d in dirs:
            path = os.path.join(directory, d)
            output.append({
                'directory':
                True,
                'name':
                d,
                'size':
                0,
                'timestamp':
                self.format_timestamp(os.path.getmtime(path)),
                'permissions':
                self.get_permissions(path),
                'owner':
                self.get_owner(path),
                'group':
                self.get_group(path)
            })

        for f in files:
            path = os.path.join(directory, f)

            try:
                size = self.human_readable_size(os.path.getsize(path))
                output.append({
                    'directory':
                    False,
                    'name':
                    f,
                    'size':
                    size,
                    'timestamp':
                    self.format_timestamp(os.path.getmtime(path)),
                    'permissions':
                    self.get_permissions(path),
                    'owner':
                    self.get_owner(path),
                    'group':
                    self.get_group(path)
                })
            except OSError:
                pass

        return output

    """ The following three methods are probably NOT the best way of doing things.
    At least according to all the warnings that say os.stat is very costly
    and should be cached."""

    def get_permissions(self, filename):
        st = os.stat(filename)
        mode = st.st_mode
        permissions = ''

        bits = [
            stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, stat.S_IRGRP,
            stat.S_IWGRP, stat.S_IXGRP, stat.S_IROTH, stat.S_IWOTH,
            stat.S_IXOTH
        ]

        attrs = ['r', 'w', 'x']

        for i in range(0, len(bits)):
            bit = bits[i]
            attr = attrs[i % len(attrs)]

            if bit & mode:
                permissions += attr
            else:
                permissions += '-'

        return permissions

    def _get_owner(self, filename):
        st = os.stat(filename)
        uid = st.st_uid
        try:
            user = pwd.getpwuid(uid)[0]
        except KeyError:
            print 'unknown uid %d for file %s' % (uid, filename)
            user = '******'
        return user

    def _get_owner_windows(self, filename):
        sd = win32security.GetFileSecurity(
            filename, win32security.OWNER_SECURITY_INFORMATION)
        owner_sid = sd.GetSecurityDescriptorOwner()
        name, domain, type = win32security.LookupAccountSid(None, owner_sid)
        return name

    def _get_group(self, filename):
        st = os.stat(filename)
        gid = st.st_gid
        try:
            groupname = grp.getgrgid(gid)[0]
        except KeyError:
            print 'unknown gid %d for file %s' % (gid, filename)
            groupname = 'unknown'
        return groupname

    def _get_group_windows(self, filename):
        return ""

    def format_timestamp(self, timestamp):
        d = datetime.datetime.fromtimestamp(timestamp)
        return d.strftime(r'%Y-%m-%d %H:%M')

    """ Like dir_scan_host, but in the connected Android device """

    def dir_scan_device(self, directory):
        output = []

        entries = self.aafm.get_device_file_list()

        dirs = []
        files = []

        for filename, entry in entries.iteritems():
            if entry['is_directory']:
                dirs.append(filename)
            else:
                files.append(filename)

        if not self.showHidden:
            files = [f for f in files if not f[0] == '.']
            dirs = [d for d in dirs if not d[0] == '.']

        dirs.sort()
        files.sort()

        output.append({
            'directory': True,
            'name': '..',
            'size': 0,
            'timestamp': '',
            'permissions': '',
            'owner': '',
            'group': ''
        })

        for d in dirs:
            output.append({
                'directory':
                True,
                'name':
                d,
                'size':
                0,
                'timestamp':
                self.format_timestamp(entries[d]['timestamp']),
                'permissions':
                entries[d]['permissions'],
                'owner':
                entries[d]['owner'],
                'group':
                entries[d]['group']
            })

        for f in files:
            size = self.human_readable_size(int(entries[f]['size']))
            output.append({
                'directory':
                False,
                'name':
                f,
                'size':
                size,
                'timestamp':
                self.format_timestamp(entries[f]['timestamp']),
                'permissions':
                entries[f]['permissions'],
                'owner':
                entries[f]['owner'],
                'group':
                entries[f]['group']
            })

        return output

    def on_host_tree_view_contextual_menu(self, widget, event):
        if event.button == 3:  # Right click
            builder = gtk.Builder()
            builder.add_from_file(
                os.path.join(self.basedir,
                             'data/glade/menu_contextual_host.xml'))
            menu = builder.get_object('menu')
            builder.connect_signals({
                'on_menuHostCopyToDevice_activate':
                self.on_host_copy_to_device_callback,
                'on_menuHostCreateDirectory_activate':
                self.on_host_create_directory_callback,
                'on_menuHostRefresh_activate':
                self.on_host_refresh_callback,
                'on_menuHostDeleteItem_activate':
                self.on_host_delete_item_callback,
                'on_menuHostRenameItem_activate':
                self.on_host_rename_item_callback
            })

            # Ensure only right options are available
            num_selected = len(self.get_host_selected_files())
            has_selection = num_selected > 0

            menuCopy = builder.get_object('menuHostCopyToDevice')
            menuCopy.set_sensitive(has_selection)

            menuDelete = builder.get_object('menuHostDeleteItem')
            menuDelete.set_sensitive(has_selection)

            menuRename = builder.get_object('menuHostRenameItem')
            menuRename.set_sensitive(num_selected == 1)

            menu.popup(None, None, None, event.button, event.time)
            return True

        # Not consuming the event
        return False

    # Copy to device
    def on_host_copy_to_device_callback(self, widget):
        for row in self.get_host_selected_files():
            src = os.path.join(self.host_cwd, row['filename'])
            self.add_to_queue(self.QUEUE_ACTION_COPY_TO_DEVICE, src,
                              self.device_cwd)
        self.process_queue()

    # Create host directory
    def on_host_create_directory_callback(self, widget):
        directory_name = self.dialog_get_directory_name()

        if directory_name is None:
            return

        full_path = os.path.join(self.host_cwd, directory_name)
        if not os.path.exists(full_path):
            os.mkdir(full_path)
            self.refresh_host_files()

    def on_host_refresh_callback(self, widget):
        self.refresh_host_files()

    def on_host_delete_item_callback(self, widget):
        selected = self.get_host_selected_files()
        items = []
        for item in selected:
            items.append(item['filename'])

        result = self.dialog_delete_confirmation(items)

        if result == gtk.RESPONSE_OK:
            for item in items:
                full_item_path = os.path.join(self.host_cwd, item)
                self.delete_item(full_item_path)
                self.refresh_host_files()

    def delete_item(self, path):
        if os.path.isfile(path):
            os.remove(path)
        else:
            shutil.rmtree(path)

    def on_host_rename_item_callback(self, widget):
        old_name = self.get_host_selected_files()[0]['filename']
        new_name = self.dialog_get_item_name(old_name)

        if new_name is None:
            return

        full_src_path = os.path.join(self.host_cwd, old_name)
        full_dst_path = os.path.join(self.host_cwd, new_name)

        shutil.move(full_src_path, full_dst_path)
        self.refresh_host_files()

    def on_device_tree_view_contextual_menu(self, widget, event):
        if event.button == 3:  # Right click
            builder = gtk.Builder()
            builder.add_from_file(
                os.path.join(self.basedir,
                             "data/glade/menu_contextual_device.xml"))
            menu = builder.get_object("menu")
            builder.connect_signals({
                'on_menuDeviceDeleteItem_activate':
                self.on_device_delete_item_callback,
                'on_menuDeviceCreateDirectory_activate':
                self.on_device_create_directory_callback,
                'on_menuDeviceRefresh_activate':
                self.on_device_refresh_callback,
                'on_menuDeviceCopyToComputer_activate':
                self.on_device_copy_to_computer_callback,
                'on_menuDeviceRenameItem_activate':
                self.on_device_rename_item_callback
            })

            # Ensure only right options are available
            num_selected = len(self.get_device_selected_files())
            has_selection = num_selected > 0
            menuDelete = builder.get_object('menuDeviceDeleteItem')
            menuDelete.set_sensitive(has_selection)

            menuCopy = builder.get_object('menuDeviceCopyToComputer')
            menuCopy.set_sensitive(has_selection)

            menuRename = builder.get_object('menuDeviceRenameItem')
            menuRename.set_sensitive(num_selected == 1)

            menu.popup(None, None, None, event.button, event.time)
            return True

        # don't consume the event, so we can still double click to navigate
        return False

    def on_device_delete_item_callback(self, widget):
        selected = self.get_device_selected_files()

        items = []

        for item in selected:
            items.append(item['filename'])

        result = self.dialog_delete_confirmation(items)

        if result == gtk.RESPONSE_OK:
            for item in items:
                full_item_path = self.aafm.device_path_join(
                    self.device_cwd, item)
                self.aafm.device_delete_item(full_item_path)
                self.refresh_device_files()

    def dialog_delete_confirmation(self, items):
        items.sort()
        joined = ', '.join(items)
        dialog = gtk.MessageDialog(
            parent=None,
            flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            type=gtk.MESSAGE_QUESTION,
            buttons=gtk.BUTTONS_OK_CANCEL,
            message_format="Are you sure you want to delete %d items?" %
            len(items))
        dialog.format_secondary_markup(
            '%s will be deleted. This action cannot be undone.' % joined)
        dialog.show_all()
        result = dialog.run()

        dialog.destroy()
        return result

    def on_device_create_directory_callback(self, widget):
        directory_name = self.dialog_get_directory_name()

        # dialog was cancelled
        if directory_name is None:
            return

        full_path = self.aafm.device_path_join(self.device_cwd, directory_name)
        self.aafm.device_make_directory(full_path)
        self.refresh_device_files()

    def dialog_get_directory_name(self):
        dialog = gtk.MessageDialog(
            None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, None)

        dialog.set_markup('Please enter new directory name:')

        entry = gtk.Entry()
        entry.connect('activate', self.dialog_response, dialog,
                      gtk.RESPONSE_OK)

        hbox = gtk.HBox()
        hbox.pack_start(gtk.Label('Name:'), False, 5, 5)
        hbox.pack_end(entry)

        dialog.vbox.pack_end(hbox, True, True, 0)
        dialog.show_all()

        result = dialog.run()

        text = entry.get_text()
        dialog.destroy()

        if result == gtk.RESPONSE_OK:
            return text
        else:
            return None

    def dialog_response(self, entry, dialog, response):
        dialog.response(response)

    def on_device_refresh_callback(self, widget):
        self.refresh_device_files()

    def on_device_copy_to_computer_callback(self, widget):
        selected = self.get_device_selected_files()
        task = self.copy_from_device_task(selected)
        gobject.idle_add(task.next)

    def copy_from_device_task(self, rows):
        completed = 0
        total = len(rows)

        self.update_progress()

        for row in rows:
            filename = row['filename']
            is_directory = row['is_directory']

            full_device_path = self.aafm.device_path_join(
                self.device_cwd, filename)
            full_host_path = self.host_cwd

            self.aafm.copy_to_host(full_device_path, full_host_path)
            completed = completed + 1
            self.refresh_host_files()
            self.update_progress(completed * 1.0 / total)

            yield True

        yield False

    def on_device_rename_item_callback(self, widget):
        old_name = self.get_device_selected_files()[0]['filename']
        new_name = self.dialog_get_item_name(old_name)

        if new_name is None:
            return

        full_src_path = self.aafm.device_path_join(self.device_cwd, old_name)
        full_dst_path = self.aafm.device_path_join(self.device_cwd, new_name)

        self.aafm.device_rename_item(full_src_path, full_dst_path)
        self.refresh_device_files()

    def dialog_get_item_name(self, old_name):
        dialog = gtk.MessageDialog(
            None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, None)

        dialog.set_markup('Please enter new name:')

        entry = gtk.Entry()
        entry.connect('activate', self.dialog_response, dialog,
                      gtk.RESPONSE_OK)
        entry.set_text(old_name)

        hbox = gtk.HBox()
        hbox.pack_start(gtk.Label('Name:'), False, 5, 5)
        hbox.pack_end(entry)

        dialog.vbox.pack_end(hbox, True, True, 0)
        dialog.show_all()

        result = dialog.run()
        text = entry.get_text()
        dialog.destroy()

        if result == gtk.RESPONSE_OK:
            return text
        else:
            return None

    def show_notice(self, notice, flash=False):
        self.showing_notice = True

        if flash:
            self.progress_bar.set_text('!' * 7)
            glib.timeout_add(1000, lambda: self.show_notice(notice))
        else:
            self.progress_bar.set_text(notice)

    def clear_notice(self):
        self.showing_notice = False
        self.update_progress(self.progress_value)

    def update_progress(self, value=None):
        if value is None:
            self.progress_bar.set_fraction(0)
            if not self.showing_notice:
                self.progress_bar.set_text("Ready")
        else:
            self.progress_bar.set_fraction(value)
            if not self.showing_notice:
                self.progress_bar.set_text("%d%%" % (value * 100))

        if value >= 1:
            if not self.showing_notice:
                self.progress_bar.set_text("Done")
            self.progress_bar.set_fraction(0)
            self.done = True

            if len(self.devices_list) > 0:
                self.devicesList.set_sensitive(True)
        else:
            self.done = False

        self.progress_value = value

    def on_host_drag_data_get(self, widget, context, selection, target_type,
                              time):
        data = '\n'.join([
            'file://' +
            urllib.quote(os.path.join(self.host_cwd, item['filename']))
            for item in self.get_host_selected_files()
        ])

        selection.set(selection.target, 8, data)

    def on_host_drag_data_received(self, tree_view, context, x, y, selection,
                                   info, timestamp):
        data = selection.data
        type = selection.type
        drop_info = tree_view.get_dest_row_at_pos(x, y)
        destination = self.host_cwd

        if drop_info:
            model = tree_view.get_model()
            path, position = drop_info

            if position in [
                    gtk.TREE_VIEW_DROP_INTO_OR_BEFORE,
                    gtk.TREE_VIEW_DROP_INTO_OR_AFTER
            ]:
                iter = model.get_iter(path)
                is_directory = model.get_value(iter, 0)
                name = model.get_value(iter, 1)

                # If dropping over a folder, copy things to that folder
                if is_directory:
                    destination = os.path.join(self.host_cwd, name)

        for line in [line.strip() for line in data.split('\n')]:
            if line.startswith('file://'):
                source = urllib.unquote(line.replace('file://', '', 1))

                if type == 'DRAG_SELF':
                    self.add_to_queue(self.QUEUE_ACTION_MOVE_IN_HOST, source,
                                      destination)
                elif type == 'ADB_text':
                    self.add_to_queue(self.QUEUE_ACTION_COPY_FROM_DEVICE,
                                      source, destination)

        self.process_queue()

    def on_device_drag_begin(self, widget, context):

        context.source_window.property_change(self.XDS_ATOM, self.TEXT_ATOM, 8,
                                              gtk.gdk.PROP_MODE_REPLACE,
                                              self.XDS_FILENAME)

    def on_device_drag_data_get(self, widget, context, selection, target_type,
                                time):

        if selection.target == 'XdndDirectSave0':
            type, format, destination_file = context.source_window.property_get(
                self.XDS_ATOM, self.TEXT_ATOM)

            if destination_file.startswith('file://'):
                destination = os.path.dirname(
                    urllib.unquote(destination_file).replace('file://', '', 1))
                for item in self.get_device_selected_files():
                    self.add_to_queue(
                        self.QUEUE_ACTION_COPY_FROM_DEVICE,
                        self.aafm.device_path_join(self.device_cwd,
                                                   item['filename']),
                        destination)

                self.process_queue()
            else:
                print "ERROR: Destination doesn't start with file://?!!?"

        else:
            selection.set(
                selection.target, 8, '\n'.join([
                    'file://' + urllib.quote(
                        self.aafm.device_path_join(self.device_cwd,
                                                   item['filename']))
                    for item in self.get_device_selected_files()
                ]))

    def on_device_drag_data_received(self, tree_view, context, x, y, selection,
                                     info, timestamp):

        data = selection.data
        type = selection.type
        drop_info = tree_view.get_dest_row_at_pos(x, y)
        destination = self.device_cwd

        # When dropped over a row
        if drop_info:
            model = tree_view.get_model()
            path, position = drop_info

            if position in [
                    gtk.TREE_VIEW_DROP_INTO_OR_BEFORE,
                    gtk.TREE_VIEW_DROP_INTO_OR_AFTER
            ]:
                iter = model.get_iter(path)
                is_directory = model.get_value(iter, 0)
                name = model.get_value(iter, 1)

                # If dropping over a folder, copy things to that folder
                if is_directory:
                    destination = self.aafm.device_path_join(
                        self.device_cwd, name)

        if type == 'DRAG_SELF':
            if self.device_cwd != destination:
                for line in [line.strip() for line in data.split('\n')]:
                    if line.startswith('file://'):
                        source = urllib.unquote(line.replace('file://', '', 1))
                        if source != destination:
                            name = self.aafm.device_path_basename(source)
                            self.add_to_queue(self.QUEUE_ACTION_MOVE_IN_DEVICE,
                                              source,
                                              os.path.join(destination, name))
        else:
            # COPY stuff
            for line in [line.strip() for line in data.split('\n')]:
                if line.startswith('file://'):
                    source = urllib.unquote(line.replace('file://', '', 1))
                    self.add_to_queue(self.QUEUE_ACTION_COPY_TO_DEVICE, source,
                                      destination)

        self.process_queue()

    def add_to_queue(self, action, src_file, dst_path):
        self.queue.append([action, src_file, dst_path])

    def process_queue(self):
        task = self.process_queue_task()
        gobject.idle_add(task.next)

    def process_queue_task(self):
        completed = 0
        self.update_progress()

        while len(self.queue) > 0:
            item = self.queue.pop()
            action, src, dst = item

            if action == self.QUEUE_ACTION_COPY_TO_DEVICE:
                self.aafm.copy_to_device(src, dst)
                self.refresh_device_files()
            if action == self.QUEUE_ACTION_COPY_FROM_DEVICE:
                self.aafm.copy_to_host(src, dst)
                self.refresh_host_files()
            elif action == self.QUEUE_ACTION_MOVE_IN_DEVICE:
                self.aafm.device_rename_item(src, dst)
                self.refresh_device_files()
            elif action == self.QUEUE_ACTION_MOVE_IN_HOST:
                shutil.move(src, dst)
                self.refresh_host_files()

            completed = completed + 1
            total = len(self.queue) + 1
            self.update_progress(completed * 1.0 / total)

            yield True

        yield False

    def refresh_devices_list(self, refreshFiles=False, defaultDevice=''):
        if not hasattr(self, 'aafm'):
            return

        self.devices_list = self.aafm.list_devices()

        self.devices_list_store.clear()
        new_active = 0
        if len(self.devices_list) > 0:
            if defaultDevice != '':
                self.aafm.set_device(defaultDevice)
            elif self.aafm.device == '':
                self.aafm.set_device(
                    self.aafm.get_device_serial(self.devices_list[0]))

            i = 0
            for device in self.devices_list:
                self.devices_list_store.append([device])

                if defaultDevice == '':
                    if self.aafm.get_device_serial(device) == self.aafm.device:
                        new_active = i
                else:
                    if self.aafm.get_device_serial(device) == defaultDevice:
                        new_active = i

                i += 1

            if self.done:
                self.devicesList.set_sensitive(True)
        else:
            self.devicesList.set_sensitive(False)
            self.devices_list_store.append(['No devices found'])
            self.device_treeViewFile.clear_data()

        self.devicesList.set_active(new_active)

        if refreshFiles:
            glib.timeout_add(750, self.refresh_device_files)

    def reset_device(self):
        if len(self.devices_list) > 0:
            self.show_notice('Lost connection to device', True)
            glib.timeout_add(750, self.refresh_device_files)
            glib.timeout_add(5000, self.clear_notice)

    def on_change_device(self, widget):
        if widget.get_active() >= 0 and len(
                self.devices_list) >= (widget.get_active() + 1):
            serial = self.aafm.get_device_serial(
                self.devices_list[widget.get_active()])
            if serial != '' and self.aafm.device != serial:
                self.aafm.set_device(serial)
                self.device_cwd = self.device_cwd_default
                self.aafm.set_device_cwd(self.device_cwd)
                self.refresh_device_files()

    def on_toggle_host_start_dir_last(self, widget):
        self.startDirHost = 'last'

    def on_toggle_host_start_dir_specific(self, widget):
        self.startDirHost = 'specific'

    def on_change_host_start_dir_path(self, widget):
        self.startDirHostPath = widget.get_text()

    def on_toggle_device_start_dir_last(self, widget):
        self.startDirDevice = 'last'

    def on_toggle_device_start_dir_specific(self, widget):
        self.startDirDevice = 'specific'

    def on_change_device_start_dir_path(self, widget):
        self.startDirDevicePath = widget.get_text()

    def on_toggle_hidden(self, widget):
        self.showHidden = widget.get_active()

        self.refresh_host_files()
        self.refresh_device_files()

    def on_clicked_add_device(self, widget):
        self.show_add_device_dialog(
            "What is the local IP address\nof your device?",
            "Add a device by IP address", self.lastDeviceIP)

    def on_clicked_refresh_devices(self, widget):
        self.refresh_devices_list()

    def on_toggle_modified(self, widget):
        self.showModified = widget.get_active()

    def on_toggle_permissions(self, widget):
        self.showPermissions = widget.get_active()

    def on_toggle_owner(self, widget):
        self.showOwner = widget.get_active()

    def on_toggle_group(self, widget):
        self.showGroup = widget.get_active()

    def open_prefs(self, widget, data=None):
        if self.window_prefs is not None:
            return False  # The window is already showing

        # Build preferences window from XML
        builder_prefs = gtk.Builder()
        builder_prefs.add_from_file(
            os.path.join(self.basedir, "data/glade/preferences.xml"))
        builder_prefs.connect_signals(
            {"on_window_destroy": self.destroy_prefs})

        hostDefaultLastDir = builder_prefs.get_object('hostDefaultLastDir')
        hostDefaultSpecificDir = builder_prefs.get_object(
            'hostDefaultSpecificDir')
        deviceDefaultLastDir = builder_prefs.get_object('deviceDefaultLastDir')
        deviceDefaultSpecificDir = builder_prefs.get_object(
            'deviceDefaultSpecificDir')

        if self.startDirHost == 'last':
            hostDefaultLastDir.set_active(True)
            hostDefaultSpecificDir.set_active(False)
        else:
            hostDefaultSpecificDir.set_active(True)
            hostDefaultLastDir.set_active(False)

        if self.startDirDevice == 'last':
            deviceDefaultLastDir.set_active(True)
            deviceDefaultSpecificDir.set_active(False)
        else:
            deviceDefaultSpecificDir.set_active(True)
            deviceDefaultLastDir.set_active(False)

        hostDefaultLastDir.connect('toggled',
                                   self.on_toggle_host_start_dir_last)
        hostDefaultSpecificDir.connect('toggled',
                                       self.on_toggle_host_start_dir_specific)
        deviceDefaultLastDir.connect('toggled',
                                     self.on_toggle_device_start_dir_last)
        deviceDefaultSpecificDir.connect(
            'toggled', self.on_toggle_device_start_dir_specific)

        hostDefaultSpecificDirPath = builder_prefs.get_object(
            'hostDefaultSpecificDirPath')
        hostDefaultSpecificDirPath.set_text(self.startDirHostPath)
        hostDefaultSpecificDirPath.connect('changed',
                                           self.on_change_host_start_dir_path)

        deviceDefaultSpecificDirPath = builder_prefs.get_object(
            'deviceDefaultSpecificDirPath')
        deviceDefaultSpecificDirPath.set_text(self.startDirDevicePath)
        deviceDefaultSpecificDirPath.connect(
            'changed', self.on_change_device_start_dir_path)

        showModified = builder_prefs.get_object('showModified')
        showModified.set_active(self.showModified)
        showModified.connect('toggled', self.on_toggle_modified)

        showPermissions = builder_prefs.get_object('showPermissions')
        showPermissions.set_active(self.showPermissions)
        showPermissions.connect('toggled', self.on_toggle_permissions)

        showOwner = builder_prefs.get_object('showOwner')
        showOwner.set_active(self.showOwner)
        showOwner.connect('toggled', self.on_toggle_owner)

        showGroup = builder_prefs.get_object('showGroup')
        showGroup.set_active(self.showGroup)
        showGroup.connect('toggled', self.on_toggle_group)

        self.window_prefs = builder_prefs.get_object("window")
        try:
            self.window_prefs.set_icon_from_file(
                os.path.join(self.basedir, "data/icons/aafm.svg"))
        except:
            self.window_prefs.set_icon_from_file(
                os.path.join(self.basedir, "data/icons/aafm.png"))

        self.window_prefs.show_all()

    def write_settings_file(self):
        try:
            with open(self.config_file_loc, 'w') as config_file:
                self.config.write(config_file)
                return True
        except IOError:
            return False

    def write_settings(self):
        self.config.set('aafm', 'lastdir_host', self.host_cwd)
        self.config.set('aafm', 'lastdir_device', self.device_cwd)
        self.config.set('aafm', 'startdir_host', self.startDirHost)
        self.config.set('aafm', 'startdir_host_path', self.startDirHostPath)
        self.config.set('aafm', 'startdir_device', self.startDirDevice)
        self.config.set('aafm', 'startdir_device_path',
                        self.startDirDevicePath)
        self.config.set('aafm', 'show_hidden',
                        'yes' if self.showHidden else 'no')
        self.config.set('aafm', 'show_modified',
                        'yes' if self.showModified else 'no')
        self.config.set('aafm', 'show_permissions',
                        'yes' if self.showPermissions else 'no')
        self.config.set('aafm', 'show_owner',
                        'yes' if self.showOwner else 'no')
        self.config.set('aafm', 'show_group',
                        'yes' if self.showGroup else 'no')
        self.config.set('aafm', 'last_serial', self.lastDeviceSerial)
        self.config.set('aafm', 'last_ip', self.lastDeviceIP)

        # Set config location to home directory if we couldn't find a working path
        if self.config_file_loc == "":
            self.config_file_loc = self.config_file_loc_default

        if not self.write_settings_file():
            # Set config location to home directory if we don't have write access to the found path
            self.config_file_loc = self.config_file_loc_default
            self.write_settings_file()

    def on_keypress_add_device(self, widget, event):
        if event.keyval == 65293:
            self.dialogWindow.emit('response', gtk.RESPONSE_OK)

    def on_response_add_device(self, widget, response):
        device_network_path = self.dialogEntry.get_text()

        self.dialogWindow.destroy()
        if (response == gtk.RESPONSE_OK) and device_network_path is not None:
            device_network_path = device_network_path.strip()
            if device_network_path.strip() != '':
                self.lastDeviceIP = device_network_path
                connected = string.join(
                    self.aafm.execute('%s connect %s' %
                                      (self.aafm.adb, device_network_path)))
                print 'Connection response: %s' % connected
                if 'already connected to' in connected:
                    self.refresh_devices_list()
                    self.show_notice(
                        'Already connected to %s' % device_network_path, True)
                    glib.timeout_add(2000, self.clear_notice)
                elif 'connected to' in connected:
                    self.show_notice('Connected to %s' % device_network_path)
                    glib.timeout_add(150,
                                     lambda: self.refresh_devices_list(True))
                    glib.timeout_add(2000, self.clear_notice)
                else:
                    self.show_notice(
                        'Unable to connect to %s' % device_network_path, True)
                    glib.timeout_add(5000, self.clear_notice)

    def show_add_device_dialog(self, message, title='', default_text=''):
        # Returns user input as a string or None
        # If user does not input text it returns None, NOT AN EMPTY STRING.
        self.dialogWindow = gtk.MessageDialog(
            self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, message)

        self.dialogWindow.set_title(title)
        self.dialogWindow.set_icon_name('')

        dialogBox = self.dialogWindow.get_content_area()
        self.dialogEntry = gtk.Entry()
        self.dialogEntry.set_text(
            default_text if default_text is not None else '')
        self.dialogEntry.connect('key-press-event',
                                 self.on_keypress_add_device)
        dialogBox.pack_end(self.dialogEntry, False, False, 0)

        self.dialogWindow.connect('response', self.on_response_add_device)
        self.dialogWindow.show_all()
        self.dialogWindow.run()

    def die_callback(self, widget, data=None):
        self.destroy(widget, data)

    def destroy_prefs(self, widget, data=None):
        self.window_prefs = None

    def destroy(self, widget, data=None):
        self.write_settings()
        gtk.main_quit()

    def main(self):
        gtk.main()
Ejemplo n.º 7
0
    def __init__(self):

        self.done = True
        self.devices_list = []
        self.showing_notice = False
        self.progress_value = None

        # Read settings
        self.config = ConfigParser.SafeConfigParser({'lastdir_host': '', 'lastdir_device': '',
                                                     'startdir_host': 'last', 'startdir_host_path': '',
                                                     'startdir_device': 'last', 'startdir_device_path': '',
                                                     'show_hidden': 'no', 'show_modified': 'yes',
                                                     'show_permissions': 'no', 'show_owner': 'no',
                                                     'show_group': 'no', 'last_serial': '',
                                                     'last_ip': ''})
        self.config_file_loc = ""
        self.config_file_loc_default = os.path.join(os.path.expanduser("~"), ".aafm")
        for file_loc in os.curdir, os.path.expanduser("~"), os.environ.get("AAFM_CONF"):
            try:
                if file_loc is not None:
                    with open(os.path.join(file_loc, ".aafm")) as source:
                        self.config.readfp(source)
                        self.config_file_loc = file_loc
                        break
            except IOError:
                pass

        # Test for the aafm section and add it if it's missing
        try:
            self.config.get("aafm", "startdir_host")
        except ConfigParser.NoSectionError:
            self.config.add_section("aafm")

        # Store config variables
        self.startDirHost = self.config.get("aafm", "startdir_host")
        self.startDirHostPath = self.config.get("aafm", "startdir_host_path")
        self.startDirDevice = self.config.get("aafm", "startdir_device")
        self.startDirDevicePath = self.config.get("aafm", "startdir_device_path")
        self.showHidden = (self.config.get("aafm", "show_hidden") == "yes")
        self.showModified = (self.config.get("aafm", "show_modified") == "yes")
        self.showPermissions = (self.config.get("aafm", "show_permissions") == "yes")
        self.showOwner = (self.config.get("aafm", "show_owner") == "yes")
        self.showGroup = (self.config.get("aafm", "show_group") == "yes")
        self.lastDeviceSerial = self.config.get("aafm", "last_serial")
        self.lastDeviceIP = self.config.get("aafm", "last_ip")

        if self.startDirHost == 'last':
            self.host_cwd = self.config.get("aafm", "lastdir_host")
        else:
            self.host_cwd = self.startDirHostPath

        if not os.path.isdir(self.host_cwd):
            self.host_cwd = os.path.expanduser("~")

        if self.startDirDevice == 'last':
            self.device_cwd_default = self.config.get("aafm", "lastdir_device")
        else:
            self.device_cwd_default = self.startDirDevicePath

        if self.device_cwd_default == '':
            self.device_cwd_default = '/mnt/sdcard'

        self.device_cwd = self.device_cwd_default

        # The super core
        self.aafm = Aafm('adb', self.host_cwd, self.device_cwd, self)
        self.queue = []

        self.basedir = os.path.dirname(os.path.abspath(__file__))

        if os.name == 'nt':
            self.get_owner = self._get_owner_windows
            self.get_group = self._get_group_windows
        else:
            self.get_owner = self._get_owner
            self.get_group = self._get_group

        # Build main window from XML
        builder = gtk.Builder()
        builder.add_from_file(os.path.join(self.basedir, "data/glade/interface.xml"))
        builder.connect_signals({"on_window_destroy": self.destroy})
        self.window = builder.get_object("window")
        vbox1 = builder.get_object("vbox1")

        # Set preferences window var
        self.window_prefs = None

        # Build menu from XML
        uimanager = gtk.UIManager()
        accelgroup = uimanager.get_accel_group()
        self.window.add_accel_group(accelgroup)

        actiongroup = gtk.ActionGroup('Main')
        actiongroup.add_actions([('Preferences', gtk.STOCK_PREFERENCES, '_Preferences', None,
                                  'Preferences', self.open_prefs),
                                 ('Quit', gtk.STOCK_QUIT, '_Quit', None,
                                  'Quit aafm', self.destroy),
                                 ('File', None, '_File')])
        uimanager.insert_action_group(actiongroup, 0)

        uimanager.add_ui_from_file(os.path.join(self.basedir, "data/glade/menu.xml"))
        menubar = uimanager.get_widget('/MenuBar')

        vbox1.pack_start(menubar, False, False, 0)
        vbox1.reorder_child(menubar, 0)

        imageDir = gtk.Image()
        imageDir.set_from_file(os.path.join(self.basedir, "data/icons/folder.png"))
        imageFile = gtk.Image()
        imageFile.set_from_file(os.path.join(self.basedir, "data/icons/file.png"))

        # Show hidden files and folders
        showHidden = builder.get_object('showHidden')
        showHidden.set_active(self.showHidden)
        showHidden.connect('toggled', self.on_toggle_hidden)

        # Progress bar
        self.progress_bar = builder.get_object('progressBar')

        # Host and device TreeViews

        # HOST
        self.host_treeViewFile = TreeViewFile(imageDir.get_pixbuf(), imageFile.get_pixbuf(), self.showModified,
                                              self.showPermissions, self.showOwner, self.showGroup)

        hostFrame = builder.get_object('frameHost')
        hostFrame.get_child().add(self.host_treeViewFile.get_view())

        hostTree = self.host_treeViewFile.get_tree()
        hostTree.connect('row-activated', self.host_navigate_callback)
        hostTree.connect('button_press_event', self.on_host_tree_view_contextual_menu)

        host_targets = [
            ('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
            ('ADB_text', 0, 1),
            ('text/plain', 0, 2)
        ]

        hostTree.enable_model_drag_dest(
            host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
        )
        hostTree.connect('drag-data-received', self.on_host_drag_data_received)

        hostTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK,
            host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
        )
        hostTree.connect('drag_data_get', self.on_host_drag_data_get)

        self.hostFrame = hostFrame
        self.hostName = socket.gethostname()


        # DEVICE
        self.device_treeViewFile = TreeViewFile(imageDir.get_pixbuf(), imageFile.get_pixbuf(), self.showModified,
                                                self.showPermissions, self.showOwner, self.showGroup)

        deviceFrame = builder.get_object('frameDevice')
        deviceFrame.get_child().add(self.device_treeViewFile.get_view())

        deviceTree = self.device_treeViewFile.get_tree()
        deviceTree.connect('row-activated', self.device_navigate_callback)
        deviceTree.connect('button_press_event', self.on_device_tree_view_contextual_menu)

        device_targets = [
            ('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
            ('ADB_text', 0, 1),
            ('XdndDirectSave0', 0, 2),
            ('text/plain', 0, 3)
        ]

        deviceTree.enable_model_drag_dest(
            device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
        )
        deviceTree.connect('drag-data-received', self.on_device_drag_data_received)

        deviceTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK,
            device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
        )
        deviceTree.connect('drag-data-get', self.on_device_drag_data_get)
        deviceTree.connect('drag-begin', self.on_device_drag_begin)

        self.deviceFrame = deviceFrame

        # Add device button
        addDevice = builder.get_object('addDevice')
        addDevice.connect('clicked', self.on_clicked_add_device)

        # Devices list combobox
        self.devicesList = builder.get_object('devicesList')
        self.devices_list_store = gtk.ListStore(gobject.TYPE_STRING)
        self.refresh_devices_list(True, self.lastDeviceSerial)
        self.devicesList.set_model(self.devices_list_store)

        cell = gtk.CellRendererText()
        self.devicesList.pack_start(cell, True)
        self.devicesList.add_attribute(cell, "text", 0)

        # if self.lastDeviceSerial in self.devices_list:
        #    self.aafm.set_device(self.lastDeviceSerial)
        #elif len(self.devices_list) > 0:
        #    self.aafm.set_device(self.aafm.get_device_serial(self.devices_list[0]))

        self.devicesList.connect('changed', self.on_change_device)

        # Devices list refresh button
        refreshDevicesList = builder.get_object('refreshDevicesList')
        refreshDevicesList.connect('clicked', self.on_clicked_refresh_devices)

        # Some more subtle details...
        try:
            self.window.set_icon_from_file(os.path.join(self.basedir, "data/icons/aafm.svg"))
        except:
            self.window.set_icon_from_file(os.path.join(self.basedir, "data/icons/aafm.png"))
            # self.adb = 'adb'

        self.refresh_host_files()

        # And we're done!
        self.window.show_all()
Ejemplo n.º 8
0
class Aafm_GUI:
    QUEUE_ACTION_COPY_TO_DEVICE = 'copy_to_device'
    QUEUE_ACTION_COPY_FROM_DEVICE = 'copy_from_device'
    QUEUE_ACTION_MOVE_IN_DEVICE = 'move_in_device'
    QUEUE_ACTION_MOVE_IN_HOST = 'move_in_host'

    # These constants are for dragging files to Nautilus
    XDS_ATOM = gtk.gdk.atom_intern("XdndDirectSave0")
    TEXT_ATOM = gtk.gdk.atom_intern("text/plain")
    XDS_FILENAME = 'whatever.txt'

    def __init__(self):

        self.done = True
        self.devices_list = []
        self.showing_notice = False
        self.progress_value = None

        # Read settings
        self.config = ConfigParser.SafeConfigParser({'lastdir_host': '', 'lastdir_device': '',
                                                     'startdir_host': 'last', 'startdir_host_path': '',
                                                     'startdir_device': 'last', 'startdir_device_path': '',
                                                     'show_hidden': 'no', 'show_modified': 'yes',
                                                     'show_permissions': 'no', 'show_owner': 'no',
                                                     'show_group': 'no', 'last_serial': '',
                                                     'last_ip': ''})
        self.config_file_loc = ""
        self.config_file_loc_default = os.path.join(os.path.expanduser("~"), ".aafm")
        for file_loc in os.curdir, os.path.expanduser("~"), os.environ.get("AAFM_CONF"):
            try:
                if file_loc is not None:
                    with open(os.path.join(file_loc, ".aafm")) as source:
                        self.config.readfp(source)
                        self.config_file_loc = file_loc
                        break
            except IOError:
                pass

        # Test for the aafm section and add it if it's missing
        try:
            self.config.get("aafm", "startdir_host")
        except ConfigParser.NoSectionError:
            self.config.add_section("aafm")

        # Store config variables
        self.startDirHost = self.config.get("aafm", "startdir_host")
        self.startDirHostPath = self.config.get("aafm", "startdir_host_path")
        self.startDirDevice = self.config.get("aafm", "startdir_device")
        self.startDirDevicePath = self.config.get("aafm", "startdir_device_path")
        self.showHidden = (self.config.get("aafm", "show_hidden") == "yes")
        self.showModified = (self.config.get("aafm", "show_modified") == "yes")
        self.showPermissions = (self.config.get("aafm", "show_permissions") == "yes")
        self.showOwner = (self.config.get("aafm", "show_owner") == "yes")
        self.showGroup = (self.config.get("aafm", "show_group") == "yes")
        self.lastDeviceSerial = self.config.get("aafm", "last_serial")
        self.lastDeviceIP = self.config.get("aafm", "last_ip")

        if self.startDirHost == 'last':
            self.host_cwd = self.config.get("aafm", "lastdir_host")
        else:
            self.host_cwd = self.startDirHostPath

        if not os.path.isdir(self.host_cwd):
            self.host_cwd = os.path.expanduser("~")

        if self.startDirDevice == 'last':
            self.device_cwd_default = self.config.get("aafm", "lastdir_device")
        else:
            self.device_cwd_default = self.startDirDevicePath

        if self.device_cwd_default == '':
            self.device_cwd_default = '/mnt/sdcard'

        self.device_cwd = self.device_cwd_default

        # The super core
        self.aafm = Aafm('adb', self.host_cwd, self.device_cwd, self)
        self.queue = []

        self.basedir = os.path.dirname(os.path.abspath(__file__))

        if os.name == 'nt':
            self.get_owner = self._get_owner_windows
            self.get_group = self._get_group_windows
        else:
            self.get_owner = self._get_owner
            self.get_group = self._get_group

        # Build main window from XML
        builder = gtk.Builder()
        builder.add_from_file(os.path.join(self.basedir, "data/glade/interface.xml"))
        builder.connect_signals({"on_window_destroy": self.destroy})
        self.window = builder.get_object("window")
        vbox1 = builder.get_object("vbox1")

        # Set preferences window var
        self.window_prefs = None

        # Build menu from XML
        uimanager = gtk.UIManager()
        accelgroup = uimanager.get_accel_group()
        self.window.add_accel_group(accelgroup)

        actiongroup = gtk.ActionGroup('Main')
        actiongroup.add_actions([('Preferences', gtk.STOCK_PREFERENCES, '_Preferences', None,
                                  'Preferences', self.open_prefs),
                                 ('Quit', gtk.STOCK_QUIT, '_Quit', None,
                                  'Quit aafm', self.destroy),
                                 ('File', None, '_File')])
        uimanager.insert_action_group(actiongroup, 0)

        uimanager.add_ui_from_file(os.path.join(self.basedir, "data/glade/menu.xml"))
        menubar = uimanager.get_widget('/MenuBar')

        vbox1.pack_start(menubar, False, False, 0)
        vbox1.reorder_child(menubar, 0)

        imageDir = gtk.Image()
        imageDir.set_from_file(os.path.join(self.basedir, "data/icons/folder.png"))
        imageFile = gtk.Image()
        imageFile.set_from_file(os.path.join(self.basedir, "data/icons/file.png"))

        # Show hidden files and folders
        showHidden = builder.get_object('showHidden')
        showHidden.set_active(self.showHidden)
        showHidden.connect('toggled', self.on_toggle_hidden)

        # Progress bar
        self.progress_bar = builder.get_object('progressBar')

        # Host and device TreeViews

        # HOST
        self.host_treeViewFile = TreeViewFile(imageDir.get_pixbuf(), imageFile.get_pixbuf(), self.showModified,
                                              self.showPermissions, self.showOwner, self.showGroup)

        hostFrame = builder.get_object('frameHost')
        hostFrame.get_child().add(self.host_treeViewFile.get_view())

        hostTree = self.host_treeViewFile.get_tree()
        hostTree.connect('row-activated', self.host_navigate_callback)
        hostTree.connect('button_press_event', self.on_host_tree_view_contextual_menu)

        host_targets = [
            ('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
            ('ADB_text', 0, 1),
            ('text/plain', 0, 2)
        ]

        hostTree.enable_model_drag_dest(
            host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
        )
        hostTree.connect('drag-data-received', self.on_host_drag_data_received)

        hostTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK,
            host_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
        )
        hostTree.connect('drag_data_get', self.on_host_drag_data_get)

        self.hostFrame = hostFrame
        self.hostName = socket.gethostname()


        # DEVICE
        self.device_treeViewFile = TreeViewFile(imageDir.get_pixbuf(), imageFile.get_pixbuf(), self.showModified,
                                                self.showPermissions, self.showOwner, self.showGroup)

        deviceFrame = builder.get_object('frameDevice')
        deviceFrame.get_child().add(self.device_treeViewFile.get_view())

        deviceTree = self.device_treeViewFile.get_tree()
        deviceTree.connect('row-activated', self.device_navigate_callback)
        deviceTree.connect('button_press_event', self.on_device_tree_view_contextual_menu)

        device_targets = [
            ('DRAG_SELF', gtk.TARGET_SAME_WIDGET, 0),
            ('ADB_text', 0, 1),
            ('XdndDirectSave0', 0, 2),
            ('text/plain', 0, 3)
        ]

        deviceTree.enable_model_drag_dest(
            device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
        )
        deviceTree.connect('drag-data-received', self.on_device_drag_data_received)

        deviceTree.enable_model_drag_source(
            gtk.gdk.BUTTON1_MASK,
            device_targets,
            gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE
        )
        deviceTree.connect('drag-data-get', self.on_device_drag_data_get)
        deviceTree.connect('drag-begin', self.on_device_drag_begin)

        self.deviceFrame = deviceFrame

        # Add device button
        addDevice = builder.get_object('addDevice')
        addDevice.connect('clicked', self.on_clicked_add_device)

        # Devices list combobox
        self.devicesList = builder.get_object('devicesList')
        self.devices_list_store = gtk.ListStore(gobject.TYPE_STRING)
        self.refresh_devices_list(True, self.lastDeviceSerial)
        self.devicesList.set_model(self.devices_list_store)

        cell = gtk.CellRendererText()
        self.devicesList.pack_start(cell, True)
        self.devicesList.add_attribute(cell, "text", 0)

        # if self.lastDeviceSerial in self.devices_list:
        #    self.aafm.set_device(self.lastDeviceSerial)
        #elif len(self.devices_list) > 0:
        #    self.aafm.set_device(self.aafm.get_device_serial(self.devices_list[0]))

        self.devicesList.connect('changed', self.on_change_device)

        # Devices list refresh button
        refreshDevicesList = builder.get_object('refreshDevicesList')
        refreshDevicesList.connect('clicked', self.on_clicked_refresh_devices)

        # Some more subtle details...
        try:
            self.window.set_icon_from_file(os.path.join(self.basedir, "data/icons/aafm.svg"))
        except:
            self.window.set_icon_from_file(os.path.join(self.basedir, "data/icons/aafm.png"))
            # self.adb = 'adb'

        self.refresh_host_files()

        # And we're done!
        self.window.show_all()


    def host_navigate_callback(self, widget, path, view_column):

        row = path[0]
        model = widget.get_model()
        iter = model.get_iter(row)
        is_dir = model.get_value(iter, 0)
        name = model.get_value(iter, 1)

        if is_dir:
            self.host_cwd = os.path.normpath(os.path.join(self.host_cwd, name))
            self.aafm.set_host_cwd(self.host_cwd)
            self.refresh_host_files()


    def device_navigate_callback(self, widget, path, view_column):

        row = path[0]
        model = widget.get_model()
        iter = model.get_iter(row)
        is_dir = model.get_value(iter, 0)
        name = model.get_value(iter, 1)

        if is_dir:
            self.device_cwd = self.aafm.device_path_normpath(self.aafm.device_path_join(self.device_cwd, name))
            self.aafm.set_device_cwd(self.device_cwd)
            self.refresh_device_files()


    def refresh_host_files(self):
        self.host_treeViewFile.load_data(self.dir_scan_host(self.host_cwd))
        self.hostFrame.set_label(self.host_cwd)


    def refresh_device_files(self):
        print 'Refreshing device files'
        if self.aafm.device != '':
            self.device_treeViewFile.load_data(self.dir_scan_device(self.device_cwd))
            self.deviceFrame.set_label(self.device_cwd)
        else:
            self.device_treeViewFile.clear_data()

    def get_treeviewfile_selected(self, treeviewfile):
        values = []
        model, rows = treeviewfile.get_tree().get_selection().get_selected_rows()

        for row in rows:
            iter = model.get_iter(row)
            filename = model.get_value(iter, 1)
            is_directory = model.get_value(iter, 0)
            values.append({'filename': filename, 'is_directory': is_directory})

        return values


    def get_host_selected_files(self):
        return self.get_treeviewfile_selected(self.host_treeViewFile)

    def get_device_selected_files(self):
        return self.get_treeviewfile_selected(self.device_treeViewFile)

    def human_readable_size(self, size):
        for x in ['B', 'K', 'M', 'G']:
            if size < 1024.0:
                return "%3.1f%s" % (size, x)
            size /= 1024.0
        return "%3.1f%s" % (size, 'T')

    """ Walks through a directory and return the data in a tree-style list
        that can be used by the TreeViewFile """

    def dir_scan_host(self, directory):
        output = []

        root, dirs, files = next(os.walk(directory))

        if not self.showHidden:
            files = [f for f in files if not f[0] == '.']
            dirs = [d for d in dirs if not d[0] == '.']

        dirs.sort()
        files.sort()

        output.append({'directory': True, 'name': '..', 'size': 0, 'timestamp': '',
                       'permissions': '',
                       'owner': '',
                       'group': ''})

        for d in dirs:
            path = os.path.join(directory, d)
            output.append({
                'directory': True,
                'name': d,
                'size': 0,
                'timestamp': self.format_timestamp(os.path.getmtime(path)),
                'permissions': self.get_permissions(path),
                'owner': self.get_owner(path),
                'group': self.get_group(path)
            })

        for f in files:
            path = os.path.join(directory, f)

            try:
                size = self.human_readable_size(os.path.getsize(path))
                output.append({
                    'directory': False,
                    'name': f,
                    'size': size,
                    'timestamp': self.format_timestamp(os.path.getmtime(path)),
                    'permissions': self.get_permissions(path),
                    'owner': self.get_owner(path),
                    'group': self.get_group(path)
                })
            except OSError:
                pass

        return output

    """ The following three methods are probably NOT the best way of doing things.
    At least according to all the warnings that say os.stat is very costly
    and should be cached."""

    def get_permissions(self, filename):
        st = os.stat(filename)
        mode = st.st_mode
        permissions = ''

        bits = [
            stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR,
            stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP,
            stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH
        ]

        attrs = ['r', 'w', 'x']

        for i in range(0, len(bits)):
            bit = bits[i]
            attr = attrs[i % len(attrs)]

            if bit & mode:
                permissions += attr
            else:
                permissions += '-'

        return permissions

    def _get_owner(self, filename):
        st = os.stat(filename)
        uid = st.st_uid
        try:
            user = pwd.getpwuid(uid)[0]
        except KeyError:
            print 'unknown uid %d for file %s' % (uid, filename)
            user = '******'
        return user

    def _get_owner_windows(self, filename):
        sd = win32security.GetFileSecurity(filename, win32security.OWNER_SECURITY_INFORMATION)
        owner_sid = sd.GetSecurityDescriptorOwner()
        name, domain, type = win32security.LookupAccountSid(None, owner_sid)
        return name

    def _get_group(self, filename):
        st = os.stat(filename)
        gid = st.st_gid
        try:
            groupname = grp.getgrgid(gid)[0]
        except KeyError:
            print 'unknown gid %d for file %s' % (gid, filename)
            groupname = 'unknown'
        return groupname

    def _get_group_windows(self, filename):
        return ""


    def format_timestamp(self, timestamp):
        d = datetime.datetime.fromtimestamp(timestamp)
        return d.strftime(r'%Y-%m-%d %H:%M')

    """ Like dir_scan_host, but in the connected Android device """

    def dir_scan_device(self, directory):
        output = []

        entries = self.aafm.get_device_file_list()

        dirs = []
        files = []

        for filename, entry in entries.iteritems():
            if entry['is_directory']:
                dirs.append(filename)
            else:
                files.append(filename)

        if not self.showHidden:
            files = [f for f in files if not f[0] == '.']
            dirs = [d for d in dirs if not d[0] == '.']

        dirs.sort()
        files.sort()

        output.append(
            {'directory': True, 'name': '..', 'size': 0, 'timestamp': '', 'permissions': '', 'owner': '', 'group': ''})

        for d in dirs:
            output.append({
                'directory': True,
                'name': d,
                'size': 0,
                'timestamp': self.format_timestamp(entries[d]['timestamp']),
                'permissions': entries[d]['permissions'],
                'owner': entries[d]['owner'],
                'group': entries[d]['group']
            })

        for f in files:
            size = self.human_readable_size(int(entries[f]['size']))
            output.append({
                'directory': False,
                'name': f,
                'size': size,
                'timestamp': self.format_timestamp(entries[f]['timestamp']),
                'permissions': entries[f]['permissions'],
                'owner': entries[f]['owner'],
                'group': entries[f]['group']
            })

        return output

    def on_host_tree_view_contextual_menu(self, widget, event):
        if event.button == 3:  # Right click
            builder = gtk.Builder()
            builder.add_from_file(os.path.join(self.basedir, 'data/glade/menu_contextual_host.xml'))
            menu = builder.get_object('menu')
            builder.connect_signals({
                'on_menuHostCopyToDevice_activate': self.on_host_copy_to_device_callback,
                'on_menuHostCreateDirectory_activate': self.on_host_create_directory_callback,
                'on_menuHostRefresh_activate': self.on_host_refresh_callback,
                'on_menuHostDeleteItem_activate': self.on_host_delete_item_callback,
                'on_menuHostRenameItem_activate': self.on_host_rename_item_callback
            })

            # Ensure only right options are available
            num_selected = len(self.get_host_selected_files())
            has_selection = num_selected > 0

            menuCopy = builder.get_object('menuHostCopyToDevice')
            menuCopy.set_sensitive(has_selection)

            menuDelete = builder.get_object('menuHostDeleteItem')
            menuDelete.set_sensitive(has_selection)

            menuRename = builder.get_object('menuHostRenameItem')
            menuRename.set_sensitive(num_selected == 1)

            menu.popup(None, None, None, event.button, event.time)
            return True

        # Not consuming the event
        return False

    # Copy to device
    def on_host_copy_to_device_callback(self, widget):
        for row in self.get_host_selected_files():
            src = os.path.join(self.host_cwd, row['filename'])
            self.add_to_queue(self.QUEUE_ACTION_COPY_TO_DEVICE, src, self.device_cwd)
        self.process_queue()


    # Create host directory
    def on_host_create_directory_callback(self, widget):
        directory_name = self.dialog_get_directory_name()

        if directory_name is None:
            return

        full_path = os.path.join(self.host_cwd, directory_name)
        if not os.path.exists(full_path):
            os.mkdir(full_path)
            self.refresh_host_files()


    def on_host_refresh_callback(self, widget):
        self.refresh_host_files()


    def on_host_delete_item_callback(self, widget):
        selected = self.get_host_selected_files()
        items = []
        for item in selected:
            items.append(item['filename'])

        result = self.dialog_delete_confirmation(items)

        if result == gtk.RESPONSE_OK:
            for item in items:
                full_item_path = os.path.join(self.host_cwd, item)
                self.delete_item(full_item_path)
                self.refresh_host_files()

    def delete_item(self, path):
        if os.path.isfile(path):
            os.remove(path)
        else:
            shutil.rmtree(path)

    def on_host_rename_item_callback(self, widget):
        old_name = self.get_host_selected_files()[0]['filename']
        new_name = self.dialog_get_item_name(old_name)

        if new_name is None:
            return

        full_src_path = os.path.join(self.host_cwd, old_name)
        full_dst_path = os.path.join(self.host_cwd, new_name)

        shutil.move(full_src_path, full_dst_path)
        self.refresh_host_files()

    def on_device_tree_view_contextual_menu(self, widget, event):
        if event.button == 3:  # Right click
            builder = gtk.Builder()
            builder.add_from_file(os.path.join(self.basedir, "data/glade/menu_contextual_device.xml"))
            menu = builder.get_object("menu")
            builder.connect_signals({
                'on_menuDeviceDeleteItem_activate': self.on_device_delete_item_callback,
                'on_menuDeviceCreateDirectory_activate': self.on_device_create_directory_callback,
                'on_menuDeviceRefresh_activate': self.on_device_refresh_callback,
                'on_menuDeviceCopyToComputer_activate': self.on_device_copy_to_computer_callback,
                'on_menuDeviceRenameItem_activate': self.on_device_rename_item_callback
            })

            # Ensure only right options are available
            num_selected = len(self.get_device_selected_files())
            has_selection = num_selected > 0
            menuDelete = builder.get_object('menuDeviceDeleteItem')
            menuDelete.set_sensitive(has_selection)

            menuCopy = builder.get_object('menuDeviceCopyToComputer')
            menuCopy.set_sensitive(has_selection)

            menuRename = builder.get_object('menuDeviceRenameItem')
            menuRename.set_sensitive(num_selected == 1)

            menu.popup(None, None, None, event.button, event.time)
            return True

        # don't consume the event, so we can still double click to navigate
        return False

    def on_device_delete_item_callback(self, widget):
        selected = self.get_device_selected_files()

        items = []

        for item in selected:
            items.append(item['filename'])

        result = self.dialog_delete_confirmation(items)

        if result == gtk.RESPONSE_OK:
            for item in items:
                full_item_path = self.aafm.device_path_join(self.device_cwd, item)
                self.aafm.device_delete_item(full_item_path)
                self.refresh_device_files()


    def dialog_delete_confirmation(self, items):
        items.sort()
        joined = ', '.join(items)
        dialog = gtk.MessageDialog(
            parent=None,
            flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            type=gtk.MESSAGE_QUESTION,
            buttons=gtk.BUTTONS_OK_CANCEL,
            message_format="Are you sure you want to delete %d items?" % len(items)
        )
        dialog.format_secondary_markup('%s will be deleted. This action cannot be undone.' % joined)
        dialog.show_all()
        result = dialog.run()

        dialog.destroy()
        return result

    def on_device_create_directory_callback(self, widget):
        directory_name = self.dialog_get_directory_name()

        # dialog was cancelled
        if directory_name is None:
            return

        full_path = self.aafm.device_path_join(self.device_cwd, directory_name)
        self.aafm.device_make_directory(full_path)
        self.refresh_device_files()


    def dialog_get_directory_name(self):
        dialog = gtk.MessageDialog(
            None,
            gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            gtk.MESSAGE_QUESTION,
            gtk.BUTTONS_OK_CANCEL,
            None)

        dialog.set_markup('Please enter new directory name:')

        entry = gtk.Entry()
        entry.connect('activate', self.dialog_response, dialog, gtk.RESPONSE_OK)

        hbox = gtk.HBox()
        hbox.pack_start(gtk.Label('Name:'), False, 5, 5)
        hbox.pack_end(entry)

        dialog.vbox.pack_end(hbox, True, True, 0)
        dialog.show_all()

        result = dialog.run()

        text = entry.get_text()
        dialog.destroy()

        if result == gtk.RESPONSE_OK:
            return text
        else:
            return None


    def dialog_response(self, entry, dialog, response):
        dialog.response(response)


    def on_device_refresh_callback(self, widget):
        self.refresh_device_files()


    def on_device_copy_to_computer_callback(self, widget):
        selected = self.get_device_selected_files()
        task = self.copy_from_device_task(selected)
        gobject.idle_add(task.next)


    def copy_from_device_task(self, rows):
        completed = 0
        total = len(rows)

        self.update_progress()

        for row in rows:
            filename = row['filename']
            is_directory = row['is_directory']

            full_device_path = self.aafm.device_path_join(self.device_cwd, filename)
            full_host_path = self.host_cwd

            self.aafm.copy_to_host(full_device_path, full_host_path)
            completed = completed + 1
            self.refresh_host_files()
            self.update_progress(completed * 1.0 / total)

            yield True

        yield False

    def on_device_rename_item_callback(self, widget):
        old_name = self.get_device_selected_files()[0]['filename']
        new_name = self.dialog_get_item_name(old_name)

        if new_name is None:
            return

        full_src_path = self.aafm.device_path_join(self.device_cwd, old_name)
        full_dst_path = self.aafm.device_path_join(self.device_cwd, new_name)

        self.aafm.device_rename_item(full_src_path, full_dst_path)
        self.refresh_device_files()

    def dialog_get_item_name(self, old_name):
        dialog = gtk.MessageDialog(
            None,
            gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            gtk.MESSAGE_QUESTION,
            gtk.BUTTONS_OK_CANCEL,
            None)

        dialog.set_markup('Please enter new name:')

        entry = gtk.Entry()
        entry.connect('activate', self.dialog_response, dialog, gtk.RESPONSE_OK)
        entry.set_text(old_name)

        hbox = gtk.HBox()
        hbox.pack_start(gtk.Label('Name:'), False, 5, 5)
        hbox.pack_end(entry)

        dialog.vbox.pack_end(hbox, True, True, 0)
        dialog.show_all()

        result = dialog.run()
        text = entry.get_text()
        dialog.destroy()

        if result == gtk.RESPONSE_OK:
            return text
        else:
            return None

    def show_notice(self, notice, flash=False):
        self.showing_notice = True

        if flash:
            self.progress_bar.set_text('!' * 7)
            glib.timeout_add(1000, lambda: self.show_notice(notice))
        else:
            self.progress_bar.set_text(notice)

    def clear_notice(self):
        self.showing_notice = False
        self.update_progress(self.progress_value)

    def update_progress(self, value=None):
        if value is None:
            self.progress_bar.set_fraction(0)
            if not self.showing_notice:
                self.progress_bar.set_text("Ready")
        else:
            self.progress_bar.set_fraction(value)
            if not self.showing_notice:
                self.progress_bar.set_text("%d%%" % (value * 100))

        if value >= 1:
            if not self.showing_notice:
                self.progress_bar.set_text("Done")
            self.progress_bar.set_fraction(0)
            self.done = True

            if len(self.devices_list) > 0:
                self.devicesList.set_sensitive(True)
        else:
            self.done = False

        self.progress_value = value

    def on_host_drag_data_get(self, widget, context, selection, target_type, time):
        data = '\n'.join(['file://' + urllib.quote(os.path.join(self.host_cwd, item['filename'])) for item in
                          self.get_host_selected_files()])

        selection.set(selection.target, 8, data)


    def on_host_drag_data_received(self, tree_view, context, x, y, selection, info, timestamp):
        data = selection.data
        type = selection.type
        drop_info = tree_view.get_dest_row_at_pos(x, y)
        destination = self.host_cwd

        if drop_info:
            model = tree_view.get_model()
            path, position = drop_info

            if position in [gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_AFTER]:
                iter = model.get_iter(path)
                is_directory = model.get_value(iter, 0)
                name = model.get_value(iter, 1)

                # If dropping over a folder, copy things to that folder
                if is_directory:
                    destination = os.path.join(self.host_cwd, name)

        for line in [line.strip() for line in data.split('\n')]:
            if line.startswith('file://'):
                source = urllib.unquote(line.replace('file://', '', 1))

                if type == 'DRAG_SELF':
                    self.add_to_queue(self.QUEUE_ACTION_MOVE_IN_HOST, source, destination)
                elif type == 'ADB_text':
                    self.add_to_queue(self.QUEUE_ACTION_COPY_FROM_DEVICE, source, destination)

        self.process_queue()


    def on_device_drag_begin(self, widget, context):

        context.source_window.property_change(self.XDS_ATOM, self.TEXT_ATOM, 8, gtk.gdk.PROP_MODE_REPLACE,
                                              self.XDS_FILENAME)


    def on_device_drag_data_get(self, widget, context, selection, target_type, time):

        if selection.target == 'XdndDirectSave0':
            type, format, destination_file = context.source_window.property_get(self.XDS_ATOM, self.TEXT_ATOM)

            if destination_file.startswith('file://'):
                destination = os.path.dirname(urllib.unquote(destination_file).replace('file://', '', 1))
                for item in self.get_device_selected_files():
                    self.add_to_queue(self.QUEUE_ACTION_COPY_FROM_DEVICE,
                                      self.aafm.device_path_join(self.device_cwd, item['filename']), destination)

                self.process_queue()
            else:
                print "ERROR: Destination doesn't start with file://?!!?"


        else:
            selection.set(selection.target, 8, '\n'.join(
                ['file://' + urllib.quote(self.aafm.device_path_join(self.device_cwd, item['filename'])) for item in
                 self.get_device_selected_files()]))


    def on_device_drag_data_received(self, tree_view, context, x, y, selection, info, timestamp):

        data = selection.data
        type = selection.type
        drop_info = tree_view.get_dest_row_at_pos(x, y)
        destination = self.device_cwd

        # When dropped over a row
        if drop_info:
            model = tree_view.get_model()
            path, position = drop_info

            if position in [gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_AFTER]:
                iter = model.get_iter(path)
                is_directory = model.get_value(iter, 0)
                name = model.get_value(iter, 1)

                # If dropping over a folder, copy things to that folder
                if is_directory:
                    destination = self.aafm.device_path_join(self.device_cwd, name)

        if type == 'DRAG_SELF':
            if self.device_cwd != destination:
                for line in [line.strip() for line in data.split('\n')]:
                    if line.startswith('file://'):
                        source = urllib.unquote(line.replace('file://', '', 1))
                        if source != destination:
                            name = self.aafm.device_path_basename(source)
                            self.add_to_queue(self.QUEUE_ACTION_MOVE_IN_DEVICE, source, os.path.join(destination, name))
        else:
            # COPY stuff
            for line in [line.strip() for line in data.split('\n')]:
                if line.startswith('file://'):
                    source = urllib.unquote(line.replace('file://', '', 1))
                    self.add_to_queue(self.QUEUE_ACTION_COPY_TO_DEVICE, source, destination)

        self.process_queue()


    def add_to_queue(self, action, src_file, dst_path):
        self.queue.append([action, src_file, dst_path])


    def process_queue(self):
        task = self.process_queue_task()
        gobject.idle_add(task.next)

    def process_queue_task(self):
        completed = 0
        self.update_progress()

        while len(self.queue) > 0:
            item = self.queue.pop()
            action, src, dst = item

            if action == self.QUEUE_ACTION_COPY_TO_DEVICE:
                self.aafm.copy_to_device(src, dst)
                self.refresh_device_files()
            if action == self.QUEUE_ACTION_COPY_FROM_DEVICE:
                self.aafm.copy_to_host(src, dst)
                self.refresh_host_files()
            elif action == self.QUEUE_ACTION_MOVE_IN_DEVICE:
                self.aafm.device_rename_item(src, dst)
                self.refresh_device_files()
            elif action == self.QUEUE_ACTION_MOVE_IN_HOST:
                shutil.move(src, dst)
                self.refresh_host_files()

            completed = completed + 1
            total = len(self.queue) + 1
            self.update_progress(completed * 1.0 / total)

            yield True

        yield False

    def refresh_devices_list(self, refreshFiles=False, defaultDevice=''):
        if not hasattr(self, 'aafm'):
            return

        self.devices_list = self.aafm.list_devices()

        self.devices_list_store.clear()
        new_active = 0
        if len(self.devices_list) > 0:
            if defaultDevice != '':
                self.aafm.set_device(defaultDevice)
            elif self.aafm.device == '':
                self.aafm.set_device(self.aafm.get_device_serial(self.devices_list[0]))

            i = 0
            for device in self.devices_list:
                self.devices_list_store.append([device])

                if defaultDevice == '':
                    if self.aafm.get_device_serial(device) == self.aafm.device:
                        new_active = i
                else:
                    if self.aafm.get_device_serial(device) == defaultDevice:
                        new_active = i

                i += 1

            if self.done:
                self.devicesList.set_sensitive(True)
        else:
            self.devicesList.set_sensitive(False)
            self.devices_list_store.append(['No devices found'])
            self.device_treeViewFile.clear_data()

        self.devicesList.set_active(new_active)

        if refreshFiles:
            glib.timeout_add(750, self.refresh_device_files)

    def reset_device(self):
        if len(self.devices_list) > 0:
            self.show_notice('Lost connection to device', True)
            glib.timeout_add(750, self.refresh_device_files)
            glib.timeout_add(5000, self.clear_notice)

    def on_change_device(self, widget):
        if widget.get_active() >= 0 and len(self.devices_list) >= (widget.get_active() + 1):
            serial = self.aafm.get_device_serial(self.devices_list[widget.get_active()])
            if serial != '' and self.aafm.device != serial:
                self.aafm.set_device(serial)
                self.device_cwd = self.device_cwd_default
                self.aafm.set_device_cwd(self.device_cwd)
                self.refresh_device_files()

    def on_toggle_host_start_dir_last(self, widget):
        self.startDirHost = 'last'

    def on_toggle_host_start_dir_specific(self, widget):
        self.startDirHost = 'specific'

    def on_change_host_start_dir_path(self, widget):
        self.startDirHostPath = widget.get_text()

    def on_toggle_device_start_dir_last(self, widget):
        self.startDirDevice = 'last'

    def on_toggle_device_start_dir_specific(self, widget):
        self.startDirDevice = 'specific'

    def on_change_device_start_dir_path(self, widget):
        self.startDirDevicePath = widget.get_text()

    def on_toggle_hidden(self, widget):
        self.showHidden = widget.get_active()

        self.refresh_host_files()
        self.refresh_device_files()

    def on_clicked_add_device(self, widget):
        self.show_add_device_dialog(
            "What is the local IP address\nof your device?",
            "Add a device by IP address",
            self.lastDeviceIP)

    def on_clicked_refresh_devices(self, widget):
        self.refresh_devices_list()

    def on_toggle_modified(self, widget):
        self.showModified = widget.get_active()

    def on_toggle_permissions(self, widget):
        self.showPermissions = widget.get_active()

    def on_toggle_owner(self, widget):
        self.showOwner = widget.get_active()

    def on_toggle_group(self, widget):
        self.showGroup = widget.get_active()

    def open_prefs(self, widget, data=None):
        if self.window_prefs is not None:
            return False  # The window is already showing

        # Build preferences window from XML
        builder_prefs = gtk.Builder()
        builder_prefs.add_from_file(os.path.join(self.basedir, "data/glade/preferences.xml"))
        builder_prefs.connect_signals({"on_window_destroy": self.destroy_prefs})

        hostDefaultLastDir = builder_prefs.get_object('hostDefaultLastDir')
        hostDefaultSpecificDir = builder_prefs.get_object('hostDefaultSpecificDir')
        deviceDefaultLastDir = builder_prefs.get_object('deviceDefaultLastDir')
        deviceDefaultSpecificDir = builder_prefs.get_object('deviceDefaultSpecificDir')

        if self.startDirHost == 'last':
            hostDefaultLastDir.set_active(True)
            hostDefaultSpecificDir.set_active(False)
        else:
            hostDefaultSpecificDir.set_active(True)
            hostDefaultLastDir.set_active(False)

        if self.startDirDevice == 'last':
            deviceDefaultLastDir.set_active(True)
            deviceDefaultSpecificDir.set_active(False)
        else:
            deviceDefaultSpecificDir.set_active(True)
            deviceDefaultLastDir.set_active(False)

        hostDefaultLastDir.connect('toggled', self.on_toggle_host_start_dir_last)
        hostDefaultSpecificDir.connect('toggled', self.on_toggle_host_start_dir_specific)
        deviceDefaultLastDir.connect('toggled', self.on_toggle_device_start_dir_last)
        deviceDefaultSpecificDir.connect('toggled', self.on_toggle_device_start_dir_specific)

        hostDefaultSpecificDirPath = builder_prefs.get_object('hostDefaultSpecificDirPath')
        hostDefaultSpecificDirPath.set_text(self.startDirHostPath)
        hostDefaultSpecificDirPath.connect('changed', self.on_change_host_start_dir_path)

        deviceDefaultSpecificDirPath = builder_prefs.get_object('deviceDefaultSpecificDirPath')
        deviceDefaultSpecificDirPath.set_text(self.startDirDevicePath)
        deviceDefaultSpecificDirPath.connect('changed', self.on_change_device_start_dir_path)

        showModified = builder_prefs.get_object('showModified')
        showModified.set_active(self.showModified)
        showModified.connect('toggled', self.on_toggle_modified)

        showPermissions = builder_prefs.get_object('showPermissions')
        showPermissions.set_active(self.showPermissions)
        showPermissions.connect('toggled', self.on_toggle_permissions)

        showOwner = builder_prefs.get_object('showOwner')
        showOwner.set_active(self.showOwner)
        showOwner.connect('toggled', self.on_toggle_owner)

        showGroup = builder_prefs.get_object('showGroup')
        showGroup.set_active(self.showGroup)
        showGroup.connect('toggled', self.on_toggle_group)

        self.window_prefs = builder_prefs.get_object("window")
        try:
            self.window_prefs.set_icon_from_file(os.path.join(self.basedir, "data/icons/aafm.svg"))
        except:
            self.window_prefs.set_icon_from_file(os.path.join(self.basedir, "data/icons/aafm.png"))

        self.window_prefs.show_all()

    def write_settings_file(self):
        try:
            with open(self.config_file_loc, 'w') as config_file:
                self.config.write(config_file)
                return True
        except IOError:
            return False

    def write_settings(self):
        self.config.set('aafm', 'lastdir_host', self.host_cwd)
        self.config.set('aafm', 'lastdir_device', self.device_cwd)
        self.config.set('aafm', 'startdir_host', self.startDirHost)
        self.config.set('aafm', 'startdir_host_path', self.startDirHostPath)
        self.config.set('aafm', 'startdir_device', self.startDirDevice)
        self.config.set('aafm', 'startdir_device_path', self.startDirDevicePath)
        self.config.set('aafm', 'show_hidden', 'yes' if self.showHidden else 'no')
        self.config.set('aafm', 'show_modified', 'yes' if self.showModified else 'no')
        self.config.set('aafm', 'show_permissions', 'yes' if self.showPermissions else 'no')
        self.config.set('aafm', 'show_owner', 'yes' if self.showOwner else 'no')
        self.config.set('aafm', 'show_group', 'yes' if self.showGroup else 'no')
        self.config.set('aafm', 'last_serial', self.lastDeviceSerial)
        self.config.set('aafm', 'last_ip', self.lastDeviceIP)

        # Set config location to home directory if we couldn't find a working path
        if self.config_file_loc == "":
            self.config_file_loc = self.config_file_loc_default

        if not self.write_settings_file():
            # Set config location to home directory if we don't have write access to the found path
            self.config_file_loc = self.config_file_loc_default
            self.write_settings_file()

    def on_keypress_add_device(self, widget, event):
        if event.keyval == 65293:
            self.dialogWindow.emit('response', gtk.RESPONSE_OK)

    def on_response_add_device(self, widget, response):
        device_network_path = self.dialogEntry.get_text()

        self.dialogWindow.destroy()
        if (response == gtk.RESPONSE_OK) and device_network_path is not None:
            device_network_path = device_network_path.strip()
            if device_network_path.strip() != '':
                self.lastDeviceIP = device_network_path
                connected = string.join(self.aafm.execute('%s connect %s' % (self.aafm.adb, device_network_path)))
                print 'Connection response: %s' % connected
                if 'already connected to' in connected:
                    self.refresh_devices_list()
                    self.show_notice('Already connected to %s' % device_network_path, True)
                    glib.timeout_add(2000, self.clear_notice)
                elif 'connected to' in connected:
                    self.show_notice('Connected to %s' % device_network_path)
                    glib.timeout_add(150, lambda: self.refresh_devices_list(True))
                    glib.timeout_add(2000, self.clear_notice)
                else:
                    self.show_notice('Unable to connect to %s' % device_network_path, True)
                    glib.timeout_add(5000, self.clear_notice)

    def show_add_device_dialog(self, message, title='', default_text=''):
        # Returns user input as a string or None
        # If user does not input text it returns None, NOT AN EMPTY STRING.
        self.dialogWindow = gtk.MessageDialog(self.window,
                                              gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                                              gtk.MESSAGE_QUESTION,
                                              gtk.BUTTONS_OK_CANCEL,
                                              message)

        self.dialogWindow.set_title(title)
        self.dialogWindow.set_icon_name('')

        dialogBox = self.dialogWindow.get_content_area()
        self.dialogEntry = gtk.Entry()
        self.dialogEntry.set_text(default_text if default_text is not None else '')
        self.dialogEntry.connect('key-press-event', self.on_keypress_add_device)
        dialogBox.pack_end(self.dialogEntry, False, False, 0)

        self.dialogWindow.connect('response', self.on_response_add_device)
        self.dialogWindow.show_all()
        self.dialogWindow.run()

    def die_callback(self, widget, data=None):
        self.destroy(widget, data)


    def destroy_prefs(self, widget, data=None):
        self.window_prefs = None

    def destroy(self, widget, data=None):
        self.write_settings()
        gtk.main_quit()

    def main(self):
        gtk.main()