Index: Lib/ftplib.py =================================================================== --- Lib/ftplib.py (revisione 88231) +++ Lib/ftplib.py (copia locale) @@ -520,6 +520,40 @@ cmd = cmd + (' ' + arg) self.retrlines(cmd, func) + def mlsd(self, path="", facts=[], callback=None): + '''List a directory in a standardized format by using MLSD + command (RFC-3659). If path is omitted the current directory + is assumed. "facts" is a list of strings representing the type + of information desired (e.g. ["type", "size", "perm"]). + Return a list of dictionaries including the file name and + a variable number of "facts", depending on the server and + whether "facts" argument has been provided. + If a callback function is specified this is called for each + line with a dict argument. + ''' + if facts: + self.sendcmd("OPTS MLST " + ";".join(facts) + ";") + lines = [] + cmd = "MLSD %s" % path if path else "MLSD" + self.retrlines(cmd, lines.append) + ls = [] + for line in lines: + name = line[line.rfind('; ') +2:].rstrip(CRLF) + facts = line[:line.rfind(';')].split(';') + entry = {} + for fact in facts: + if "=" in fact: + key, value = fact.split("=") + if value.isdigit(): + value = int(value) + entry[key] = value + entry['name'] = name + if callback is not None: + callback(entry) + else: + ls.append(entry) + return ls + def rename(self, fromname, toname): '''Rename a file.''' resp = self.sendcmd('RNFR ' + fromname) Index: Lib/test/test_ftplib.py =================================================================== --- Lib/test/test_ftplib.py (revisione 88231) +++ Lib/test/test_ftplib.py (copia locale) @@ -26,6 +26,9 @@ RETR_DATA = 'abcde12345\r\n' * 1000 LIST_DATA = 'foo\r\nbar\r\n' NLST_DATA = 'foo\r\nbar\r\n' +MLSD_DATA = """type=file;size=156;perm=r;modify=20071029155301;unique=801cd2; music.mp3\r\n +type=dir;size=0;perm=el;modify=20071127230206;unique=801e33; ebooks\r\n +type=file;size=211;perm=r;modify=20071103093626;unique=801e32; module.py\r\n""" class DummyDTPHandler(asynchat.async_chat): @@ -208,6 +211,14 @@ self.dtp.push(NLST_DATA) self.dtp.close_when_done() + def cmd_opts(self, arg): + self.push('200 opts ok') + + def cmd_mlsd(self, arg): + self.push('125 mlsd ok') + self.dtp.push(MLSD_DATA) + self.dtp.close_when_done() + class DummyFTPServer(asyncore.dispatcher, threading.Thread): @@ -550,6 +561,12 @@ self.client.dir(lambda x: l.append(x)) self.assertEqual(''.join(l), LIST_DATA.replace('\r\n', '')) + def test_mlsd(self): + self.client.mlsd() + self.client.mlsd(path='/') + self.client.mlsd(path='/', facts=['size', 'type']) + self.client.mlsd(path='/', facts=['size', 'type'], callback=lambda x: x) + def test_makeport(self): with self.client.makeport(): # IPv4 is in use, just make sure send_eprt has not been used Index: Doc/library/ftplib.rst =================================================================== --- Doc/library/ftplib.rst (revisione 88231) +++ Doc/library/ftplib.rst (copia locale) @@ -307,6 +307,19 @@ in :meth:`transfercmd`. +.. method:: FTP.mlsd(path="", facts=[], callback=None): + + List a directory in a standardized format by using MLSD command + (:rfc:`3659`). If *path* is omitted the current directory is assumed. + *facts* is a list of strings representing the type of information desired + (e.g. *["type", "size", "perm"]*). Return a list of dictionaries including + the file name and a variable number of "facts", depending on the server and + whether *facts* argument has been provided. If a callback function is + specified this is called for each line with a dict argument. + + .. versionadded:: 3.3 + + .. method:: FTP.nlst(argument[, ...]) Return a list of file names as returned by the ``NLST`` command. The