browsewin.py :  » Network » FtpCube » ftpcube-0.5.1 » libftpcube » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Network » FtpCube 
FtpCube » ftpcube 0.5.1 » libftpcube » browsewin.py
"""
FtpCube
Copyright (C) Michael Gilfix

This file is part of FtpCube.

You should have received a file COPYING containing license terms
along with this program; if not, write to Michael Gilfix
(mgilfix@eecs.tufts.edu) for a copy.

This version of FtpCube is open source; you can redistribute it and/or
modify it under the terms listed in the file COPYING.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
"""

import browser
import cache
import chmodwin
import dispatcher
import events
import icons.folder
import icons.link
import icons.file
import logger
import messages
import protocol
import utils
from logger import Logger

import wx

import os
import re
import time
import sys

class AbstractFileWindow(wx.Panel):
    """Base class for file browsing windows.

    A file browsing window provides a directory navigation window, where the window displays
    the contents of a directory. A file window allows for sorting of the displayed files,
    with directories first and hiding of hidden entries (those that start with a '.'). The
    file display can be sorted by various columns, including name, size, and date.

    An abstact file window handles two types of events by default: double clicks that
    perform entry into a directory and right clicks which bring up a popup menu. The
    popup menu should be populated with actions that can be performed on the entry for the
    given type of file window."""

    # Sorting constants
    UNSORTED = 0
    SORT_BY_NAME = 1
    SORT_BY_SIZE = 2
    SORT_BY_DATE = 3

    idLIST = wx.NewId()

    HIDDEN_RE = re.compile("^\..*")

    def __init__(self, parent, headers):
        """Creates a displays the file window.

        'headers' is a list of headers for columns in the file window."""
        wx.Panel.__init__(self, parent, -1)

        self.list = wx.ListCtrl(self, self.idLIST, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
        for col, name in zip(range(len(headers)), headers):
            self.list.InsertColumn(col, name)

        self.image_list = wx.ImageList(15, 17)
        self.folder_index = self.image_list.Add(icons.folder.getBitmap())
        self.link_index = self.image_list.Add(icons.link.getBitmap())
        self.file_index = self.image_list.Add(icons.file.getBitmap())
        self.list.SetImageList(self.image_list, wx.IMAGE_LIST_SMALL)

        status_bar = self.makeStatusBar()

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list, 1, wx.EXPAND)
        sizer.Add(status_bar, 0, wx.EXPAND)
        self.SetAutoLayout(True)
        self.SetSizer(sizer)

        # Set instance variables
        self.sorted = self.SORT_BY_NAME
        self.hidden_files = True
        self.directories_first = True
        self.dir = None

        self.list.Bind(wx.EVT_LEFT_DCLICK, self.onDoubleClick)
        self.list.Bind(wx.EVT_RIGHT_DOWN, self.onRightClick)

    def makeStatusBar(self):
        """Creates the status bar for displaying the current directory and any transfer
        information."""
        self.status_label = wx.StaticText(self, -1, "")
        hsizer = wx.BoxSizer(wx.HORIZONTAL)
        hsizer.Add((5, 1))
        hsizer.Add(self.status_label, 1, wx.EXPAND)
        vsizer = wx.BoxSizer(wx.VERTICAL)
        vsizer.Add((0, 3))
        vsizer.Add(hsizer, 0, wx.EXPAND)
        vsizer.Add((0, 3))
        return vsizer

    def getStatusBar(self):
        """Returns a wx StaticText object that is the status bar."""
        return self.status_label

    def updateStatusBar(self, dir):
        """Updates the status bar with the specified directory text."""
        status_bar = self.getStatusBar()
        status_bar.SetLabel(dir)

    def selectBitmapIndex(self, flags):
        """Selects the appropriate bitmap to use given a series of file flags.

        File flags are in the string form used in unix file listings."""
        if not flags:
            return self.folder_index
        if flags[0] == 'l':
            return self.link_index
        elif flags[0] == 'd':
            return self.folder_index
        else:
            return self.file_index

    def setDir(self, dir):
        """Sets the current directory for the file window."""
        self.dir = dir

    def getDir(self):
        """Returns the current directory for the file window."""
        return self.dir

    def onDoubleClick(self, event):
        """Handles a double click event on the file window."""
        raise NotImplementedError

    def onRightClick(self, event):
        """Handles a right click event on the file window."""
        raise NotImplementedError

    def toggleHide(self, event=None):
        """Toggles the hidden files flag."""
        self.hidden_files = bool((self.hidden_files + 1) % 2)
        self.updateListing('.')

    def toggleDirectoriesFirst(self, event=None):
        """Toggles the directories first flag."""
        self.directories_first = bool((self.directories_first + 1) % 2)
        self.updateListing('.')

    def updateListing(self, dir):
        """Updates the file window to reflect the listing for the specified directory."""
        raise NotImplementedError

    def unsorted(self, event=None):
        """Sets the sort mode to unsorted and updates the file listing."""
        self.sorted = self.UNSORTED
        self.directories_first = False
        self.updateListing('.')

    def sortByName(self, event=None):
        """Sets the sort mode to sorting by name and updates the file listing."""
        self.sorted = self.SORT_BY_NAME
        self.directories_first = True
        self.updateListing('.')

    def sortBySize(self, event=None):
        """Sets the sort mode to sorting by size and updates the file listing."""
        self.sorted = self.SORT_BY_SIZE
        self.directories_first = True
        self.updateListing('.')

    def sortByDate(self, event=None):
        """Sets the sort mode to sorting by date and updates the file listing."""
        self.sorted = self.SORT_BY_DATE
        self.directories_first = True
        self.updateListing('.')

    def clearList(self):
        """Clears the current list of files."""
        if self.list:
            self.list.Freeze()
            self.list.DeleteAllItems()
            self.list.Thaw()

    def sortList(self, sequence):
        """Performs the sorting of a sequence of entries for the file list.

        The actual sorting of the list is implementation dependent."""
        raise NotImplementedError

    def performSort(self, sequence):
        """Performs a low-level sorting of a list names.

        A sorted list of file entries is returned. The sort mode is taken into account while
        sorting. This method does distinguish between sorting directories first."""
        if self.sorted == self.SORT_BY_NAME:
            sequence.sort(lambda x, y: cmp(x[0], y[0]))
        elif self.sorted == self.SORT_BY_SIZE:
            sequence.sort(lambda x, y: cmp(long(x[4]), long(y[4])))
        elif self.sorted == self.SORT_BY_DATE:
            # Convert the times beforehand for speed
            try:
                converted = [ (i, time.strptime(i[2], "%b %d %H:%M")) for i in sequence ]
            except Exception:
                return sequence
            converted.sort(lambda x, y: comp(x[1], y[1]))
            sequence = [ i[0] for i in converted ]
        return sequence

    def getFileSize(self, file):
        """Returns the size of the specified file name."""
        index = self.list.FindItem(-1, file)
        return utils.getColumnText(self.list, index, 2)

    def getMainThread(self):
        """Returns the main thread from the application thread manager."""
        thread_mgr = utils.getAppThreadManager()
        return thread_mgr.getMainThread()

class LocalWindow(AbstractFileWindow):
    """The local file browsing window.

    This window allows for browsing the local filesystem. All operations in popup menus are
    performed on local files. Browsing is subject to the permissions of directories and
    files. Operations on local files that result in tranfers to remote systems make use of
    the main connection thread for execution."""

    # Popup IDs
    idPOPUP_UPLOAD  = wx.NewId()
    idPOPUP_RENAME  = wx.NewId()
    idPOPUP_DELETE  = wx.NewId()
    idPOPUP_CREATE  = wx.NewId()
    idPOPUP_CHMOD   = wx.NewId()
    idPOPUP_CHANGE  = wx.NewId()
    idPOPUP_REFRESH = wx.NewId()
    idPOPUP_HIDDEN  = wx.NewId()
    idPOPUP_UNSORT  = wx.NewId()
    idPOPUP_BYNAME  = wx.NewId()
    idPOPUP_BYSIZE  = wx.NewId()
    idPOPUP_BYDATE  = wx.NewId()
    idPOPUP_ORDER   = wx.NewId()

    TIME_RE = re.compile(('[A-Za-z]+\s+([A-Za-z]+\s+\d+\s+\d+:\d+):(\d+)\s+\d+'))

    def __init__(self, parent):
        """Creates the local file browsing window and starts the window in the default
        directory for the local OS."""
        if __debug__:
            print "Making local file browsing window."
        self.headers = [ _("Filename"), _("Size"), _("Date"), _("Flags") ]
        AbstractFileWindow.__init__(self, parent, self.headers)

        # Set the date alignment
        utils.setListColumnAlignment(self.list, 2, wx.LIST_FORMAT_RIGHT)

        # Set the heading sizes accordingly
        for i in range(len(self.headers)):
            self.list.SetColumnWidth(i, 100)
        # Explicitly make the filenames column bigger
        self.list.SetColumnWidth(0, 200)

        # Open up the default directory for display
        cwd = None
        if sys.platform == 'win32' and os.environ.has_key('HOMEPATH') and \
           os.environ.has_key('HOMEDRIVE'):
            cwd = os.environ['HOMEDRIVE'] + os.environ['HOMEPATH']
        elif os.name == 'posix' and os.environ.has_key('HOME'):
            cwd = os.environ['HOME']
        if cwd is None:
            cwd = os.getcwd()
        self.setDir(cwd)

        self.last_listing = None
        self.updateListing(self.getDir())

        self.Bind(events.EVT_LOCAL_WINDOW, self.onLocalWindowEvent)
        evt_registry = events.getEventRegistry()
        evt_registry.registerEventListener(events.EVT_LOCAL_WINDOW_TYPE, self)

    def onDoubleClick(self, event):
        """Handles a double-click event.

        If a directory is double clicked, then that directory is entered. If a file is
        double-clicked, then a no-op occurs."""
        item = self.list.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED)
        if item != -1:
            selected = utils.getColumnText(self.list, item, 0)
            self.updateListing(selected)

    def onRightClick(self, event):
        """Creates a popup menu at the location of the right click."""
        menu = self.makePopupMenu()
        self.PopupMenu(menu, event.GetPosition())

    def onLocalWindowEvent(self, event):
        """Handles custom local window events.

        Local window events are an update to the window and a refresh of the window.
        The update uses the event message as the updated directory and causes a change
        in the view. The refresh reloads the current directory."""
        if event.kind == events.LocalWindowEvent.EVT_LIST_UPDATE:
            self.updateListing(event.msg)
        elif event.kind == events.LocalWindowEvent.EVT_LIST_REFRESH:
            self.onRefresh(event)

    def makePopupMenu(self):
        """Creates a popup menu containing the list of supported actions for local file
        objects."""
        menu = wx.Menu()
        menu.Append(self.idPOPUP_UPLOAD, _("Upload Files"))
        menu.Append(self.idPOPUP_RENAME, _("Rename Files"))
        menu.Append(self.idPOPUP_DELETE, _("Delete Selected Files"))
        menu.AppendSeparator()
        menu.Append(self.idPOPUP_CREATE, _("Create Directory"))
        menu.Append(self.idPOPUP_CHMOD, _("Change Permissions"))
        menu.Append(self.idPOPUP_CHANGE, _("Change Directory"))
        menu.Append(self.idPOPUP_REFRESH, _("Refresh Listing"))
        menu.AppendSeparator()
        menu.AppendCheckItem(self.idPOPUP_HIDDEN, _("Hide Hidden Files"))
        menu.AppendSeparator()

        # Create the sort order sub menu and add it
        sort_menu = wx.Menu()
        sort_menu.Append(self.idPOPUP_UNSORT, _("Unsorted"))
        sort_menu.Append(self.idPOPUP_BYNAME, _("Sort By Name"))
        sort_menu.Append(self.idPOPUP_BYSIZE, _("Sort By Size"))
        sort_menu.Append(self.idPOPUP_BYDATE, _("Sort By Date"))
        sort_menu.AppendSeparator()
        sort_menu.AppendCheckItem(self.idPOPUP_ORDER, _("Order Directories-Links-Files"))
        menu.AppendMenu(wx.NewId(), _("Sort Order"), sort_menu)

        if self.hidden_files:
            menu_item = menu.FindItemById(self.idPOPUP_HIDDEN)
            menu_item.Check(True)
        if self.directories_first:
            menu_item = menu.FindItemById(self.idPOPUP_ORDER)
            menu_item.Check(True)

        self.Bind(wx.EVT_MENU, self.onUpload, id=self.idPOPUP_UPLOAD)
        self.Bind(wx.EVT_MENU, self.onRename, id=self.idPOPUP_RENAME)
        self.Bind(wx.EVT_MENU, self.onDelete, id=self.idPOPUP_DELETE)
        self.Bind(wx.EVT_MENU, self.onCreate, id=self.idPOPUP_CREATE)
        self.Bind(wx.EVT_MENU, self.onChmod, id=self.idPOPUP_CHMOD)
        self.Bind(wx.EVT_MENU, self.onChange, id=self.idPOPUP_CHANGE)
        self.Bind(wx.EVT_MENU, self.onRefresh, id=self.idPOPUP_REFRESH)
        self.Bind(wx.EVT_MENU, self.toggleHide, id=self.idPOPUP_HIDDEN)
        self.Bind(wx.EVT_MENU, self.unsorted, id=self.idPOPUP_UNSORT)
        self.Bind(wx.EVT_MENU, self.sortByName, id=self.idPOPUP_BYNAME)
        self.Bind(wx.EVT_MENU, self.sortBySize, id=self.idPOPUP_BYSIZE)
        self.Bind(wx.EVT_MENU, self.sortByDate, id=self.idPOPUP_BYDATE)
        self.Bind(wx.EVT_MENU, self.toggleDirectoriesFirst, id=self.idPOPUP_ORDER)
        return menu

    def onUpload(self, event):
        """Submits the selected item to the transfer queue for uploading to the remote
        site.

        Submission is made by posting a queue event to the thread manager. This method
        also tries to record the remote size of the file from the remote file window to
        determine the resuming size."""
        selected = utils.getSelected(self.list)
        main_thread = utils.getAppThreadManager().getMainThread()
        if not main_thread:
            return

        opts = main_thread.getOptions()
        if opts is None:
            return
        remote_win = utils.getRemoteWindow()

        for item in selected:
            file = utils.getColumnText(self.list, item, 0)
            if file == '..':
                continue
            path = os.path.join(self.getDir(), file)
            flags = self.last_listing[self.list.GetItemData(item)][3]

            # Check if the file exists remotely. If so, replace the file's size with the
            # remote size. Later, we'll check if the size is smaller and perform a resume
            # if it is
            remote_size = remote_win.getTrueFileSize(file)
            if remote_size is None:
                try:
                    remote_size = os.path.getsize(path)
                except OSError:
                    remote_size = 0

            event = events.EnqueueEvent(
                file         = file,
                host         = opts['host'],
                port         = opts['port'],
                username     = opts['username'],
                password     = opts['password'],
                size         = remote_size,
                flags        = flags[0], # Just provide the type flag
                remote_path  = remote_win.getDir(),
                local_path   = self.getDir(),
                direction    = protocol.ProtocolInterface.UPLOAD,
                attempt      = 1,
                max_attempts = opts['retries'],
                method       = utils.getTransferMethod(),
                transport    = opts['transport'],
            )
            evt_registry = events.getEventRegistry()
            evt_registry.postEvent(event)

    def onRename(self, event):
        """Performs a rename the selected entries."""
        selected = utils.getSelected(self.list)
        for item in selected:
            file = utils.getColumnText(self.list, item, 0)
            orig_path = os.path.join(self.getDir(), file)
            new = messages.displayInputDialog(self, _("Ftpcube - Rename"),
                _("Rename %(f)s to:") %{ 'f' : file })
            if new:
                new_path = os.path.join(self.getDir(), new)
                try:
                    os.rename(orig_path, new_path)
                except OSError, strerror:
                    messages.displayErrorDialog(self,
                        _("Error renaming file %(orig)s to %(new)s: %(err)s")
                        %{ 'orig' : orig_path, 'new' : new_path, 'err' : strerror })
                    return
        self.updateListing(self.getDir())

    def onDelete(self, event):
        """Deletes the selected entries from the filesystem."""
        selected = utils.getSelected(self.list)
        for item in selected:
            file = utils.getColumnText(self.list, item, 0)
            type = self.last_listing[self.list.GetItemData(item)][3]
            path = os.path.join(self.getDir(), file)
            if type[0] in '-lf':
                try:
                    os.remove(path)
                except OSError, strerror:
                    messages.displayErrorDialog(self, _("Error removing file: %(err)s")
                        %{ 'err' : strerror })
            else:
                try:
                    os.rmdir(path)
                except OSError, strerror:
                    messages.displayErrorDialog(self, _("Error removing directory: %(err)s")
                        %{ 'err' : strerror })
            self.list.SetItemState(item, 0, wx.LIST_STATE_SELECTED)
        self.updateListing(self.getDir())

    def onCreate(self, event):
        """Creates a new directory in the current directory.

        An input dialog box is displayed to get the new directory name."""
        dir = messages.displayInputDialog(self, _("Ftpcube - Create Directory"),
            _("Directory Name:"))
        if dir:
            path = os.path.join(self.getDir(), dir)
            try:
                os.mkdir(path)
            except OSError, strerror:
                messages.displayErrorDialog(self, _("Error creating directory: %(err)s")
                    %{ 'err' : strerror })
                return
            self.updateListing(self.getDir())

    def onChmod(self, event):
        """Displays the change permissions window to change the selected files permissions."""
        selected = utils.getSelected(self.list)
        for item in selected:
            file = utils.getColumnText(self.list, item, 0)
            path = os.path.join(self.getDir(), file)
            try:
                info = os.stat(path)
                perm = info[0] # st_mode
            except OSError, strerror:
                messages.displayErrorDialog(self,
                    _("Error obtaining permissions for %(f)s: %(err)s")
                    %{ 'f' : file, 'err' : strerror })
                continue

            chmod_win = chmodwin.ChmodWindow(self, file, perm)
            ret = chmod_win.ShowModal()
            if ret == wx.ID_OK:
                new_perm = chmod_win.getPermissions()
                try:
                    os.chmod(path, new_perm)
                except OSError, strerror:
                    messages.displayErrorDialog(self,
                        _("Error setting permissions for %(f)s: %(err)s")
                        %{ 'f' : file, 'err' : strerror })
        if selected:
            self.updateListing(self.getDir())

    def onChange(self, event):
        """Processes a change directory event.

        This displays the local browser window, which allows the user to navigate and
        select the desired directory. The results of this window is used to change
        the current listing."""
        local_browser = browser.LocalBrowser(self, self.getDir())
        ret = local_browser.ShowModal()
        if ret == wx.ID_OK:
            dir = local_browser.getDirectory()
            if dir:
                self.updateListing(dir)

    def onRefresh(self, event):
        """Refreshes the current listing."""
        self.updateListing('.')

    def updateListing(self, dir):
        """Updates the displayed listing for the specified directory.

        This method provides special handling for the '.' and '..' directory entries."""
        dir = os.path.normpath(dir)

        # Construct our new path
        if dir == '..':
            self.setDir(os.path.dirname(self.getDir()))
        elif dir != '.':
            newpath = os.path.join(self.getDir(), dir)
            if os.path.isdir(newpath):
                self.setDir(newpath)
            else:
                messages.displayErrorDialog(self, _("Invalid directory: %(path)s")
                    %{ 'path' : newpath })

        # Get the new listing
        self.last_listing = self.readDir(self.getDir())
        self.list.Freeze()
        self.clearList()
        if self.sorted:
            parent_entry = self.last_listing.pop(0)
            self.last_listing = self.sortList(self.last_listing)
            # Make sure the parent entry is first
            self.last_listing.insert(0, parent_entry)
        for i in range(len(self.last_listing)):
            item = self.last_listing[i]
            img_index = self.selectBitmapIndex(item[3])
            self.list.InsertImageStringItem(i, item[0], img_index)
            self.list.SetStringItem(i, 1, item[1])
            self.list.SetStringItem(i, 2, item[2])
            self.list.SetStringItem(i, 3, item[3])
            self.list.SetItemData(i, i)
        self.list.Thaw()

        self.updateStatusBar(self.getDir())

    def readDir(self, dir):
        """Reads the contents of the specified directory and returns a list of tuples that
        corresponds to the appropriate columns for the local file browsing window."""
        try:
            files = os.listdir(dir)
        except OSError, strerror:
            messages.displayErrorDialog(self, _("Error reading directory: %(err)s")
                %{ 'err' : strerror })
            return

        if self.hidden_files:
            files = [ i for i in files if not self.HIDDEN_RE.match(i) ]

        # Add the special file '..'
        files.insert(0, '..')

        listitems = [ ]
        for f in files:
            # Attempt to retrieve the file info and modification times
            try:
                # lstat is preferrable for systems that support it
                if os.name == 'posix':
                    (st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size,
                     st_atime, st_mtime, st_ctime) = os.lstat(os.path.join(dir, f))
                else:
                     (st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size,
                      st_atime, st_mtime, st_ctime) = os.stat(os.path.join(dir, f))
            except OSError, strerror:
                # Set the size and mtime to 0 then and carry on if we can't stat the
                # file for some reason but know it's there
                st_size = 0
                st_mtime = 0

            # Beautify the size and time for human consumption
            size = utils.beautifySize(st_size)
            match = self.TIME_RE.match(time.ctime(st_mtime))
            mtime = match.group(1)

            # Set the appropriate file type and determine the mode
            mode = self.convertPermToStr(st_mode)
            if os.path.islink(os.path.join(self.getDir(), f)):
                mode = 'l' + mode
            elif os.path.isdir(os.path.join(self.getDir(), f)):
                mode = 'd' + mode
            else:
                mode = '-' + mode
            listitems.append((f, size, mtime, mode, str(st_size)))
        return listitems

    def convertPermToStr(self, perm):
        """Converts an octal permission into a string representation.

        The string format follows the commonly used format for representing permissions
        in unix."""
        flags = [ '-' ] * 9
        if perm & (4 << 6):
            flags[0] = 'r'
        if perm & (2 << 6):
            flags[1] = 'w'
        if perm & (1 << 6):
            flags[2] = 'x'
        if perm & (4 << 3):
            flags[3] = 'r'
        if perm & (2 << 3):
            flags[4] = 'w'
        if perm & (1 << 3):
            flags[5] = 'x'
        if perm & 4:
            flags[6] = 'r'
        if perm & 2:
            flags[7] = 'w'
        if perm & 1:
            flags[8] = 'x'
        return ''.join(flags)

    def sortList(self, list):
        """Sorts a list of file entries.

        This sort method separates directories first if that flag is set. Otherwise, the
        straight-forward low-level sequent sorting method 'performSort' is used."""
        if self.directories_first:
            dirs  = [ i for i in list if i[3][0] == 'd' ]
            links = [ i for i in list if i[3][0] == 'l' ]
            files = [ i for i in list if i[3][0] in '-f' ]

            list = [ ]
            list.extend(self.performSort(dirs))
            list.extend(self.performSort(links))
            list.extend(self.performSort(files))
        else:
            list = self.performSort(list)
        return list

class RemoteWindow(AbstractFileWindow):
    """Remote site browsing window.

    This window allows for browsing directory structures and files on a remote site.
    The directory cache is used to allow rapid browsing of already known directory
    structures. This window supports numerous operations on remote files through a
    right-click popup menu. Remote transfer events are submitted to the thread manager
    queue for execution of the transfer."""

    # Popup Menu IDs
    idPOPUP_DOWNLOAD = wx.NewId()
    idPOPUP_RENAME   = wx.NewId()
    idPOPUP_DELETE   = wx.NewId()
    idPOPUP_CREATE   = wx.NewId()
    idPOPUP_CHANGE   = wx.NewId()
    idPOPUP_CHMOD    = wx.NewId()
    idPOPUP_REFRESH  = wx.NewId()
    idPOPUP_UNSORT   = wx.NewId()
    idPOPUP_BYNAME   = wx.NewId()
    idPOPUP_BYSIZE   = wx.NewId()
    idPOPUP_BYDATE   = wx.NewId()
    idPOPUP_ORDER    = wx.NewId()
    idPOPUP_CLRCACHE = wx.NewId()

    # Regular expressions
    LIST_RE = re.compile('([a-z-]+) \s+ (\d+) \s+ (\S+) \s+ (\S+) \s+ (\d+) \s+ ' +
                         '([a-z\d]+\s+[a-z\d]+\s+[\d:]+) \s+ (.*)', re.IGNORECASE + re.VERBOSE)
    MSWIN_LIST_RE = re.compile('([a-z-]+) \s+ (\d+) \s+ ([a-z]+) \s+ (\d+) \s+ ' +
                               '([a-z]+\s+\d+\s+[\d:]+) \s+ (.*)', re.IGNORECASE + re.VERBOSE)
    MSDOS_LIST_RE = re.compile('([\d-]+\s+[a-z\d:]+) \s+ (?: \<(\w+)\> | (\d+) ) ' +
                               '\s+ (.*)', re.IGNORECASE + re.VERBOSE)
    LINK_RE = re.compile('(.*) -> (.*)')
    BROKEN_LINK_RE = re.compile('.*-> (.*)')
    TIME_RE = re.compile('[A-Za-z]+\s+([A-Za-z]+\s+\d+\s+\d+:\d+):(\d+)\s+\d+')

    # Remote separator
    REMOTE_SEP = '/'

    def __init__(self, parent):
        """Creates and displays the remote file browsing window."""
        if __debug__:
            print "Making remote file browsing window."
        self.headers = [ _("Filename"), _("Size"), _("Date"), _("Flags") ]
        AbstractFileWindow.__init__(self, parent, self.headers)

        # Set up the headers
        utils.setListColumnAlignment(self.list, 2, wx.LIST_FORMAT_RIGHT)
        for i in range(len(self.headers)):
            self.list.SetColumnWidth(i, 100)
        # Explicitly make the filenames column bigger
        self.list.SetColumnWidth(0, 200)

        self.cache = cache.DirectoryCacheTree()

        self.Bind(events.EVT_REMOTE_WINDOW, self.onRemoteWindowEvent)
        evt_registry = events.getEventRegistry()
        evt_registry.registerEventListener(events.EVT_REMOTE_WINDOW_TYPE, self)

    def onDoubleClick(self, event):
        """Processes a double click.

        If a directory is double clicked, then the directory is entered. If a file is
        double-clicked, then this handler serves as a no-op."""
        item = self.list.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED)
        if item != -1:
            selected = utils.getColumnText(self.list, item, 0)
            thread = self.getMainThread()
            if selected == '..':
                old_dir = self.getDir()
                mydir = self.dirname(old_dir)
                clogger = logger.ConsoleLogger()
                handler = CwdErrorHandler(old_dir, self, clogger)
                thread.cwd('..', err_handler=handler)
                self.updateStatusBar(mydir)
                self.setDir(mydir)
                self.getListing()
            else:
                type = utils.getColumnText(self.list, item, 3)
                if type[0] in 'ld':
                    old_dir = self.getDir()
                    mydir = self.join([ old_dir, selected ])
                    self.changeDirectory(mydir, old_dir)

    def dirname(self, dir):
        """Gets the base directory from a directory name.

        The remote separator is used rather than the OS separator."""
        index = dir.rfind(self.REMOTE_SEP)
        if index == 0:
            return self.REMOTE_SEP
        else:
            return dir[:index]

    def normpath(self, path):
        """Normalizes the specified path.

        This eliminates any sub '.' or '..' entries from the path."""
        dirs = path.split(self.REMOTE_SEP)
        pathlist = [ ]
        i = 0
        while i < len(dirs):
            if dirs[i] == '.':
                pass
            elif dirs[i] == '..':
                pathlist = pathlist[0:i-2]
            elif dirs[i]:
                pathlist.append(dirs[i])
            i = i + 1
        return self.REMOTE_SEP + self.REMOTE_SEP.join(pathlist)

    def join(self, args):
        """Joins together a list of path elements by the remote separator and returns a
        normalized path."""
        return self.normpath(self.REMOTE_SEP.join(args))

    def changeDirectory(self, dir, old_dir):
        """Changes the remote directory to the newly specified directory.

        If an error occurs, then the 'old_dir' is used to change back to the previous
        remote directory."""
        main_thread = self.getMainThread()
        if main_thread is not None:
            console_logger = logger.ConsoleLogger()
            handler = CwdErrorHandler(old_dir, self, console_logger)
            self.updateStatusBar(dir)
            self.setDir(dir)
            main_thread.cwd(dir, err_handler=handler)
            self.getListing()

    def onRightClick(self, event):
        """Processes a right click by displaying a popup menu at the click point."""
        menu = self.makePopupMenu()
        self.PopupMenu(menu, event.GetPosition())

    def makePopupMenu(self):
        """Creates the popup menu containing the list of remote operations."""
        menu = wx.Menu()
        menu.Append(self.idPOPUP_DOWNLOAD, _("Download Selected"))
        menu.Append(self.idPOPUP_RENAME, _("Rename Selected"))
        menu.Append(self.idPOPUP_CHMOD, _("Change Permissions"))
        menu.Append(self.idPOPUP_DELETE, _("Delete Selected"))
        menu.AppendSeparator()
        menu.Append(self.idPOPUP_CREATE, _("Create Directory"))
        menu.Append(self.idPOPUP_CHANGE, _("Change Directory"))
        menu.Append(self.idPOPUP_REFRESH, _("Refresh Cache"))
        menu.AppendSeparator()

        sort_menu = wx.Menu()
        sort_menu.Append(self.idPOPUP_UNSORT, _("Unsorted"))
        sort_menu.Append(self.idPOPUP_BYNAME, _("Sort by Name"))
        sort_menu.Append(self.idPOPUP_BYSIZE, _("Sort by Size"))
        sort_menu.Append(self.idPOPUP_BYDATE, _("Sort by Date"))
        sort_menu.AppendSeparator()
        sort_menu.AppendCheckItem(self.idPOPUP_ORDER, _("Order Directories-Links-Files"))
        menu.AppendMenu(wx.NewId(), _("Sort Order"), sort_menu)

        menu.AppendSeparator()
        menu.Append(self.idPOPUP_CLRCACHE, _("Clear Directory Cache"))

        if self.directories_first:
            menu_item = menu.FindItemById(self.idPOPUP_ORDER)
            menu_item.Check(True)

        self.Bind(wx.EVT_MENU, self.onDownload, id=self.idPOPUP_DOWNLOAD)
        self.Bind(wx.EVT_MENU, self.onRename, id=self.idPOPUP_RENAME)
        self.Bind(wx.EVT_MENU, self.onDelete, id=self.idPOPUP_DELETE)
        self.Bind(wx.EVT_MENU, self.onCreate, id=self.idPOPUP_CREATE)
        self.Bind(wx.EVT_MENU, self.onChange, id=self.idPOPUP_CHANGE)
        self.Bind(wx.EVT_MENU, self.onChmod, id=self.idPOPUP_CHMOD)
        self.Bind(wx.EVT_MENU, self.onRefresh, id=self.idPOPUP_REFRESH)
        self.Bind(wx.EVT_MENU, self.unsorted, id=self.idPOPUP_UNSORT)
        self.Bind(wx.EVT_MENU, self.sortByName, id=self.idPOPUP_BYNAME)
        self.Bind(wx.EVT_MENU, self.sortBySize, id=self.idPOPUP_BYSIZE)
        self.Bind(wx.EVT_MENU, self.sortByDate, id=self.idPOPUP_BYDATE)
        self.Bind(wx.EVT_MENU, self.toggleDirectoriesFirst, id=self.idPOPUP_ORDER)
        self.Bind(wx.EVT_MENU, self.clearCache, id=self.idPOPUP_CLRCACHE)
        return menu

    def updateStatusBar(self, dir):
        """Updates the status bar indicating the current directory or information about
        processing the current directory."""
        AbstractFileWindow.updateStatusBar(self, dir)
        event = events.DirectoryChangeEvent(dir)
        evt_registry = events.getEventRegistry()
        evt_registry.postEvent(event)

    def onRemoteWindowEvent(self, event):
        """Processes a custom remote window event.

        The following events are supported: An update listing event for display of data
        within the window, a refresh event for the current directory contents, and a
        change of the directory status bar event."""
        if event.kind == events.RemoteWindowEvent.EVT_LIST_UPDATE:
            listing = event.msg
            self.displayFreshListing(listing)
        elif event.kind == events.RemoteWindowEvent.EVT_LIST_REFRESH:
            if event.msg == self.getDir():
                self.onRefresh(event)
        elif event.kind == events.RemoteWindowEvent.EVT_CWD:
            self.updateStatusBar(event.msg)
            self.setDir(event.msg)

    def onDownload(self, event):
        """Processes a download event for a selected list of files and directories.

        Each entry is submitted to the transfer queue for execution of the transfer
        by a connection thread."""
        selected = utils.getSelected(self.list)
        main_thread = self.getMainThread()
        if not main_thread:
            return

        opts = main_thread.getOptions()
        for item in selected:
            file = utils.getColumnText(self.list, item, 0)
            if file == '..':
                continue
            flags = utils.getColumnText(self.list, item, 3)

            listdata = self.cache.findNode(self.getDir())
            if listdata is None or listdata.data is None:
                continue

            # Extract true size
            truesize = 0L
            for x in listdata.data:
                if x[0] == file:
                    truesize = long(x[4])
                    break

            event = events.EnqueueEvent(
                file = file,
                host = opts['host'],
                port = opts['port'],
                username = opts['username'],
                password = opts['password'],
                size = truesize,
                flags = flags[0],
                remote_path = self.getDir(),
                local_path = utils.getLocalWindow().getDir(),
                direction = protocol.ProtocolInterface.DOWNLOAD,
                attempt = 1,
                max_attempts = opts['retries'],
                method = utils.getTransferMethod(),
                transport = opts['transport'],
            )
            evt_registry = events.getEventRegistry()
            evt_registry.postEvent(event)

    def onRename(self, event):
        """Processes a rename event for all selected file."""
        selected = utils.getSelected(self.list)
        thread = self.getMainThread()
        for item in selected:
            file = utils.getColumnText(self.list, item, 0)
            if file == '..':
                continue
            new_name = messages.displayInputDialog(self, _("Ftpcube - Rename"),
                _("Rename %(f)s to:") %{ 'f' : file })
            thread.rename(file, new_name)
        thread.list()

    def onDelete(self, event):
        """Processes a delete event for all selected items."""
        selected = utils.getSelected(self.list)
        thread = self.getMainThread()
        for item in selected:
            file = utils.getColumnText(self.list, item, 0)
            if file == '..':
                continue
            type = utils.getColumnText(self.list, item, 3)
            if type[0] in '-lf':
                thread.delete(file)
            else:
                thread.rmdir(file)
        thread.list()

    def onCreate(self, event):
        """Creates a new remote directory."""
        thread = self.getMainThread()
        dir = messages.displayInputDialog(self, _("Ftpcube - Create Remote Directory"),
            _("Directory name:"))
        if dir and not dir =='..':
            thread.mkdir(dir)
            thread.list()

    def onChange(self, event):
        """Displays the remote browser window to allow the user to navigate the remote
        directory structure and select a directory to change to.

        The remote browser makes uses of the cached directory structures and only reflects
        the directories that have been previous traversed to. Otherwise, the input box
        can be used to input a previously unvisited directory."""
        remote_browser = browser.RemoteBrowser(self, self.cache, self.getDir())
        ret = remote_browser.ShowModal()
        if ret == wx.ID_OK:
            dir = remote_browser.getDirectory()
            if dir:
                self.changeDirectory(dir, self.getDir())

    def onChmod(self, event):
        """Brings up the change permissions window to specify permissions to change for
        the remote file."""
        selected = utils.getSelected(self.list)
        thread = self.getMainThread()
        for item in selected:
            file = utils.getColumnText(self.list, item, 0)
            if file == '..':
                continue
            current_perm = self.getFilePermission(item)
            chmod_win = chmodwin.ChmodWindow(self, file, current_perm)
            ret = chmod_win.ShowModal()
            if ret == wx.ID_OK:
                perm = chmod_win.getPermissions()
                perm_str = oct(perm)
                thread.chmod(file, perm_str)

    def getFilePermission(self, selected):
        """Extracts the file permission from a unix text representation into an octal
        format."""
        flags = utils.getColumnText(self.list, selected, 3)
        if not flags:
            return None
        # Grab the permissions part
        flags = flags[1:]

        mode = 0
        if flags[0] == 'r':
            mode |= (4 << 6)
        if flags[1] == 'w':
            mode |= (2 << 6)
        if flags[2] == 'x':
            mode |= (1 << 6)
        if flags[3] == 'r':
            mode |= (4 << 3)
        if flags[4] == 'w':
            mode |= (2 << 3)
        if flags[5] == 'x':
            mode |= (1 << 3)
        if flags[6] == 'r':
            mode |= 4
        if flags[7] == 'w':
            mode |= 2
        if flags[8] == 'x':
            mode |= 1
        return mode

    def onRefresh(self, event):
        """Performs a refresh of the listing on the current remote directory."""
        node = self.cache.findNode(self.getDir())
        if node is not None:
            node.data = None
        thread = self.getMainThread()
        thread.list()

    def clearCache(self, event=None):
        """Clears the remote directory cache."""
        del self.cache
        self.cache = cache.DirectoryCacheTree()

    def getTrueFileSize(self, file):
        """Gets the real remote file size (not the beautified printed size) in bytes.

        If the file cannot be found in the current listing, then None is returned."""
        listing = self.getListing()
        if listing:
            for entry in listing:
                entry = self.parseListEntry(entry)
                if entry is not None and entry[0] == file:
                    return entry[4]
        return None

    def getListing(self):
        """Gets the listing for the current directory.

        An attempt is made to retrieve the listing from the cache. If the listing does
        not exist in the cache, then a remote listing is executed."""
        path_node = self.cache.findNode(self.getDir())
        if path_node is None or path_node.data is None:
            thread = self.getMainThread()
            thread.list()
        else:
            self.setList(path_node.data)

    def setList(self, list):
        """Sets the contents of the browser window to match the specified listing."""
        if self.sorted:
            list = self.sortList(list)
        self.list.Freeze()
        self.clearList()
        for i in range(len(list)):
            item = list[i]
            img_index = self.selectBitmapIndex(item[3])
            self.list.InsertImageStringItem(i, item[0], img_index)
            self.list.SetStringItem(i, 1, item[1])
            self.list.SetStringItem(i, 2, item[2])
            self.list.SetStringItem(i, 3, item[3])
            self.list.SetItemData(i, i)
        self.list.Thaw()

    def getList(self):
        """Gets the cotents of the browser window's current listing in python list form."""
        list =[ ]
        count = self.list.GetItemCount()
        for i in range(count):
            entry = [ ]
            for j in range(4):
                entry.append(utils.getColumnText(self.list, i, j))
            list.append(tuple(entry))
        return list

    def updateListing(self, dir):
        """Updates the listing for the specified directory.

        Special handling is provided for the '.' and '..' entries so they work as
        expected."""
        if dir == '.':
            pass
        else:
            if dir =='..':
                dir = self.join([ dir, self.getDir() ])
            self.changeDirectory(dir, self.getDir())
        self.getListing()

    def displayFreshListing(self, listing):
        """Updates the list to display the result of a fresh listing from a remote file
        tranfer site."""
        listitems = [ ]
        cache_node = self.cache.addNode(self.getDir())

        for entry in listing:
            parsed = self.parseListEntry(entry)
            if parsed is None:
                continue
            file, truesize, date, flags = parsed

            # Make the size more human readable
            size = utils.beautifySize(truesize)

            # Check if the file is a link and if so, clean up the file name
            if flags and flags[0] == 'l':
                match = self.LINK_RE.match(file)
                if match is not None:
                    file, dest = match.groups()
                else:
                    # Try the broken link match
                    match = self.BROKEN_LINK_RE.match(file)
                    if match is not None:
                        file = match.group(1)
                        # Extract the file
                        index = file.rfind(self.REMOTE_SEP) + 1
                        file = file[index:]
                    else:
                        if __debug__:
                            print "WARNING: Match failed for %s" %file
            listitems.append((file, size, date, flags, str(truesize)))

        # Check if the server provided us with a '..' entry. Otherwise, make one
        found = [ i for i in listitems if i[0] == '..' ]
        if not found:
            match = self.TIME_RE.match(time.ctime(time.time()))
            localtime = match.group(1)
            dotdotentry = ( '..', _("0 Bytes"), localtime, '', '0')
            listitems.insert(0, dotdotentry)

        # Keep the listing around in case we need it
        cache_node.data = listitems
        self.setList(listitems)

    def parseListEntry(self, entry):
        """Parses a remote file listing.

        Returns a tuple containg the (file, size, date, flags) of the remote listing entry."""
        result = None            

        # Try the usual unix matching
        match = self.LIST_RE.match(entry)
        if match:
            flags, hardlinks, user, group, size, date, file = match.groups()
            result = (file, long(size), date, flags)

        # Try the MS ftp list (UNIX-like) matching
        match = self.MSWIN_LIST_RE.match(entry)
        if match:
            flags, group, user, size, date, file = match.groups()
            result = (file, long(size), date, flags)

        # Try the MS ftp list (DOS) matching
        match = self.MSDOS_LIST_RE.match(entry)
        if match:
            date, flags, size, file = match.groups()
            result = (file, long(size or 0), date, (flags or '-').lower())

        if __debug__:
            if result is None:
                print "WARNING: listing match failed for [%s]" %entry
        return result

    def sortList(self, list):
        """Performs sorting of the remote listing."""
        sorted = [ ]
        if self.directories_first:
            dirs  = [ i for i in list if not i[3] or i[3][0] == 'd' ]
            links = [ i for i in list if i[3] and i[3][0] == 'l' ]
            files = [ i for i in list if i[3] and i[3][0] in 'f-' ]

            sorted.extend(self.performSort(dirs))
            sorted.extend(self.performSort(links))
            sorted.extend(self.performSort(files))
        else:
            sorted.extend(self.performSort(list))
        return sorted

class CwdErrorHandler(dispatcher.ErrorHandler):
    """Change of working directory error handler.

    This error handler is called when an attempt to change a working directory fails. This
    results in a change back to the previous directory."""

    def __init__(self, dir, remote_win, logger):
        """Creates an error handler where 'dir' points to the old directory."""
        dispatcher.ErrorHandler.__init__(self)
        self.dir = dir
        self.remote_win = remote_win
        self.logger = logger

    def handle(self, error, dispatcher):
        """Handles a remote error by setting the current working directory back to the old
        directory specified at the creation of the handler."""
        if self.logger:
            self.logger.log(Logger.ERROR, error)
        self.remote_win.updateStatusBar(self.dir)
        self.remote_win.setDir(self.dir)
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.