Utilities/FtpUtilities.py

changeset 2047
739aa1717df5
child 2050
585f6646bf50
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utilities/FtpUtilities.py	Mon Sep 17 19:38:48 2012 +0200
@@ -0,0 +1,300 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing some FTP related utilities.
+"""
+
+from PyQt4.QtCore import QObject, QDate, QDateTime, QTime
+from PyQt4.QtNetwork import QUrlInfo
+
+
+class FtpDirLineParserError(Exception):
+    """
+    Exception class raised, if a parser issue was detected.
+    """
+
+
+class FtpDirLineParser(QObject):
+    """
+    Class to parse lines returned by a FTP LIST command.
+    """
+    MonthnamesNumbers = {
+        "jan": 1,
+        "feb": 2,
+        "mar": 3,
+        "apr": 4,
+        "may": 5,
+        "jun": 6,
+        "jul": 7,
+        "aug": 8,
+        "sep": 9,
+        "oct": 10,
+        "nov": 11,
+        "dec": 12,
+    }
+    
+    UnixMode = 0
+    WindowsMode = 1
+    MacMode = 2
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object (QObject)
+        """
+        super().__init__(parent)
+        
+        self.__parseLine = self.__parseUnixLine
+        self.__modeSwitchAllowed = True
+    
+    def __ignoreLine(self, line):
+        """
+        Private method to check, if the line should be ignored.
+        
+        @param line to check (string)
+        @return flag indicating to ignore the line (boolean)
+        """
+        return line.strip() == "" or \
+               line.strip().lower().startswith("total ")
+    
+    def __parseUnixMode(self, modeString, urlInfo):
+        """
+        Private method to parse a Unix mode string modifying the
+        given URL info object.
+        
+        @param modeString mode string to be parsed (string)
+        @param urlInfo reference to the URL info object (QUrlInfo)
+        @exception FtpDirLineParserError Raised if the mode cannot be parsed.
+        """
+        if len(modeString) != 10:
+            raise FtpDirLineParserError("invalid mode string '{0}'".format(modeString))
+        
+        modeString = modeString.lower()
+        
+        permission = 0
+        if modeString[1] != '-':
+            permission |= QUrlInfo.ReadOwner
+        if modeString[2] != '-':
+            permission |= QUrlInfo.WriteOwner
+        if modeString[3] != '-':
+            permission |= QUrlInfo.ExeOwner
+        if modeString[4] != '-':
+            permission |= QUrlInfo.ReadGroup
+        if modeString[5] != '-':
+            permission |= QUrlInfo.WriteGroup
+        if modeString[6] != '-':
+            permission |= QUrlInfo.ExeGroup
+        if modeString[7] != '-':
+            permission |= QUrlInfo.ReadOther
+        if modeString[8] != '-':
+            permission |= QUrlInfo.WriteOther
+        if modeString[9] != '-':
+            permission |= QUrlInfo.ExeOther
+        urlInfo.setPermissions(permission)
+        
+        if modeString[0] == "d":
+            urlInfo.setDir(True)
+            urlInfo.setFile(False)
+        elif modeString[0] == "l":
+            urlInfo.setSymLink(True)
+        elif modeString[0] == "-":
+            urlInfo.setDir(False)
+            urlInfo.setFile(True)
+    
+    def __parseUnixTime(self, monthAbbreviation, day, yearOrTime, urlInfo):
+        """
+        Private method to parse a Unix date and time indication modifying
+        the given URL info object.
+        
+
+        Date time strings in Unix-style directory listings typically
+        have one of these formats:
+        <ul>
+          <li>"Nov 23 02:33" (month name, day of month, time)</li>
+          <li>"May 26  2005" (month name, day of month, year)</li>
+        </ul>
+        
+        @param monthAbbreviation abbreviation of the month name (string)
+        @param day day of the month (string)
+        @param yearOrTime string giving the year or a time (string)
+        @param urlInfo reference to the URL info object (QUrlInfo)
+        @exception FtpDirLineParserError Raised if the month abbreviation is
+            not recognized.
+        """
+        try:
+            month = FtpDirLineParser.MonthnamesNumbers[monthAbbreviation.lower()]
+        except KeyError:
+            raise FtpDirLineParserError("illegal month abbreviation '{0}'".format(
+                monthAbbreviation))
+        day = int(day)
+        if ':' in yearOrTime:
+            year = QDate.currentDate().year()
+            hour, minute = yearOrTime.split(':')
+            hour = int(hour)
+            minute = int(minute)
+        else:
+            year = int(yearOrTime)
+            hour = 0
+            minute = 0
+        
+        lastModified = QDateTime(QDate(year, month, day), QTime(hour, minute))
+        urlInfo.setLastModified(lastModified)
+    
+    def __splitUnixLine(self, line):
+        """
+        Split a line of a Unix like directory listing into meta data,
+        number of links, user, group, size, month, day, year or time
+        and name.
+        
+        @return tuple of nine strings giving the meta data,
+            number of links, user, group, size, month, day, year or time
+            and name
+        @exception FtpDirLineParserError Raised if the line is not of a
+            recognized Unix format.
+        """
+        # This method encapsulates the recognition of an unusual
+        # Unix format variant.
+        lineParts = line.split()
+        fieldCountWithoutUserID = 8
+        fieldCountWithUserID = fieldCountWithoutUserID + 1
+        if len(lineParts) < fieldCountWithoutUserID:
+            raise FtpDirLineParserError("line '{0}' cannot be parsed".format(line))
+        
+        # If we have a valid format (either with or without user id field),
+        # the field with index 5 is either the month abbreviation or a day.
+        try:
+            int(lineParts[5])
+        except ValueError:
+            # Month abbreviation, "invalid literal for int"
+            lineParts = line.split(None, fieldCountWithUserID - 1)
+        else:
+            # Day
+            lineParts = line.split(None, fieldCountWithoutUserID - 1)
+            userFieldIndex = 2
+            lineParts.insert(userFieldIndex, "")
+        
+        return lineParts
+    
+    def __parseUnixLine(self, line):
+        """
+        Private method to parse a Unix style directory listing line.
+        
+        @param line directory line to be parsed (string)
+        @return URL info object containing the valid data (QUrlInfo)
+        @exception FtpDirLineParserError Raised if the line is not of a
+            recognized Unix format.
+        """
+        modeString, nlink, user, group, size, month, day, \
+          yearOrTime, name = self.__splitUnixLine(line)
+        
+        urlInfo = QUrlInfo()
+        self.__parseUnixMode(modeString, urlInfo)
+        self.__parseUnixTime(month, day, yearOrTime, urlInfo)
+        urlInfo.setOwner(user)
+        urlInfo.setGroup(group)
+        urlInfo.setSize(int(size))
+        name = name.strip()
+        i = name.find(" -> ")
+        if i >= 0:
+            name = name[:i]
+        urlInfo.setName(name)
+        
+        return urlInfo
+    
+    def __parseWindowsTime(self, date, time, urlInfo):
+        """
+        Private method to parse a Windows date and time indication modifying
+        the given URL info object.
+
+        Date time strings in Windows-style directory listings typically
+        have the format "10-23-12 03:25PM" (month-day_of_month-two_digit_year,
+        hour:minute, am/pm).
+        
+        @param date date string (string)
+        @param time time string (string)
+        @param urlInfo reference to the URL info object (QUrlInfo)
+        @exception FtpDirLineParserError Raised if either of the strings is not
+            recognized.
+        """
+        try:
+            month, day, year = [int(part) for part in date.split('-')]
+            if year >= 70:
+                year = 1900 + year
+            else:
+                year = 2000 + year
+        except (ValueError, IndexError):
+            raise FtpDirLineParserError("illegal date string '{0}'".format(month))
+        try:
+            hour, minute, am_pm = time[0:2], time[3:5], time[5]
+            hour = int(hour)
+            minute = int(minute)
+        except (ValueError, IndexError):
+            raise FtpDirLineParserError("illegal time string '{0}'".format(month))
+        if hour == 12 and am_pm == 'A':
+            hour = 0
+        if hour != 12 and am_pm == 'P':
+            hour = hour + 12
+        
+        lastModified = QDateTime(QDate(year, month, day), QTime(hour, minute))
+        urlInfo.setLastModified(lastModified)
+    
+    def __parseWindowsLine(self, line):
+        """
+        Private method to parse a Windows style directory listing line.
+        
+        @param line directory line to be parsed (string)
+        @return URL info object containing the valid data (QUrlInfo)
+        @exception FtpDirLineParserError Raised if the line is not of a
+            recognized Windows format.
+        """
+        try:
+            date, time, dirOrSize, name = line.split(None, 3)
+        except ValueError:
+            # "unpack list of wrong size"
+            raise FtpDirLineParserError("line '{0}' cannot be parsed".format(line))
+        
+        urlInfo = QUrlInfo()
+        self.__parseWindowsTime(date, time, urlInfo)
+        if dirOrSize.lower() == "<dir>":
+            urlInfo.setDir(True)
+            urlInfo.setFile(False)
+        else:
+            urlInfo.setDir(False)
+            urlInfo.setFile(True)
+            try:
+                urlInfo.setSize(int(dirOrSize))
+            except ValueError:
+                raise FtpDirLineParserError("illegal size '{0}'".format(dirOrSize))
+        urlInfo.setName(name)
+        
+        return urlInfo
+    
+    def parseLine(self, line):
+        """
+        Private method to parse a directory listing line.
+        
+        This implementation support Unix and Windows style directory
+        listings. It tries Unix style first and if that fails switches
+        to Windows style. If that fails as well, an exception is raised.
+        
+        @param line directory line to be parsed (string)
+        @return URL info object containing the valid data (QUrlInfo)
+        @exception FtpDirLineParserError Raised if the line is not of a
+            recognized format.
+        """
+        if self.__ignoreLine(line):
+            return None
+        
+        try:
+            return self.__parseLine(line)
+        except FtpDirLineParserError:
+            if self.__modeSwitchAllowed:
+                self.__parseLine = self.__parseWindowsLine
+                self.__modeSwitchAllowed = False
+                return self.__parseLine(line)
+            else:
+                raise

eric ide

mercurial