5 |
5 |
6 """ |
6 """ |
7 Module implementing a network reply class for FTP resources. |
7 Module implementing a network reply class for FTP resources. |
8 """ |
8 """ |
9 |
9 |
10 from PyQt4.QtCore import QByteArray, QIODevice, Qt, QUrl, QTimer, QBuffer |
10 import ftplib |
|
11 import socket |
|
12 import errno |
|
13 |
|
14 from PyQt4.QtCore import QByteArray, QIODevice, Qt, QUrl, QTimer, QBuffer, QDate, QTime, \ |
|
15 QDateTime, QCoreApplication |
11 from PyQt4.QtGui import QPixmap |
16 from PyQt4.QtGui import QPixmap |
12 from PyQt4.QtNetwork import QFtp, QNetworkReply, QNetworkRequest, QUrlInfo, \ |
17 from PyQt4.QtNetwork import QNetworkReply, QNetworkRequest, QUrlInfo |
13 QNetworkProxyQuery, QNetworkProxy, QAuthenticator |
|
14 from PyQt4.QtWebKit import QWebSettings |
18 from PyQt4.QtWebKit import QWebSettings |
15 |
19 |
16 import UI.PixmapCache |
20 import UI.PixmapCache |
17 |
21 |
18 ftpListPage_html = """\ |
22 ftpListPage_html = """\ |
104 """ |
123 """ |
105 super().__init__(parent) |
124 super().__init__(parent) |
106 |
125 |
107 self.__manager = parent |
126 self.__manager = parent |
108 |
127 |
109 self.__ftp = QFtp(self) |
128 self.__ftp = ftplib.FTP() |
110 self.__ftp.listInfo.connect(self.__processListInfo) |
|
111 self.__ftp.readyRead.connect(self.__processData) |
|
112 self.__ftp.commandFinished.connect(self.__processCommand) |
|
113 self.__ftp.commandStarted.connect(self.__commandStarted) |
|
114 self.__ftp.dataTransferProgress.connect(self.downloadProgress) |
|
115 |
129 |
116 self.__items = [] |
130 self.__items = [] |
117 self.__content = QByteArray() |
131 self.__content = QByteArray() |
118 self.__units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] |
132 self.__units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] |
119 |
133 |
120 if url.path() == "": |
134 if url.path() == "": |
121 url.setPath("/") |
135 url.setPath("/") |
122 self.setUrl(url) |
136 self.setUrl(url) |
123 # do proxy setup |
|
124 query = QNetworkProxyQuery(url) |
|
125 proxyList = parent.proxyFactory().queryProxy(query) |
|
126 ftpProxy = QNetworkProxy() |
|
127 for proxy in proxyList: |
|
128 if proxy.type() == QNetworkProxy.NoProxy or \ |
|
129 proxy.type() == QNetworkProxy.FtpCachingProxy: |
|
130 ftpProxy = proxy |
|
131 break |
|
132 if ftpProxy.type() == QNetworkProxy.DefaultProxy: |
|
133 self.setError(QNetworkReply.ProxyNotFoundError, |
|
134 self.trUtf8("No suitable proxy found.")) |
|
135 QTimer.singleShot(0, self.__errorSignals) |
|
136 return |
|
137 elif ftpProxy.type() == QNetworkProxy.FtpCachingProxy: |
|
138 self.__ftp.setProxy(ftpProxy.hostName(), ftpProxy.port()) |
|
139 |
137 |
140 self.__loggingIn = False |
138 self.__loggingIn = False |
141 |
139 |
142 QTimer.singleShot(0, self.__connectToHost) |
140 QTimer.singleShot(0, self.__doFtpCommands) |
143 |
|
144 def __errorSignals(self): |
|
145 """ |
|
146 Private slot to send signal for errors during initialisation. |
|
147 """ |
|
148 self.error.emit(QNetworkReply.ProxyNotFoundError) |
|
149 self.finished.emit() |
|
150 |
141 |
151 def abort(self): |
142 def abort(self): |
152 """ |
143 """ |
153 Public slot to abort the operation. |
144 Public slot to abort the operation. |
154 """ |
145 """ |
182 len_ = min(maxlen, self.__content.size()) |
173 len_ = min(maxlen, self.__content.size()) |
183 buffer = bytes(self.__content[:len_]) |
174 buffer = bytes(self.__content[:len_]) |
184 self.__content.remove(0, len_) |
175 self.__content.remove(0, len_) |
185 return buffer |
176 return buffer |
186 |
177 |
187 def __connectToHost(self): |
178 def __doFtpCommands(self): |
188 """ |
179 """ |
189 Private slot to start the FTP process by connecting to the host. |
180 Private slot doing the sequence of FTP commands to get the requested result. |
190 """ |
181 """ |
191 self.__ftp.connectToHost(self.url().host()) |
182 try: |
192 |
183 self.__ftp.connect(self.url().host(), timeout=10) |
193 def __commandStarted(self, id): |
|
194 """ |
|
195 Private slot to handle the start of FTP commands. |
|
196 |
|
197 @param id id of the command to be processed (integer) (ignored) |
|
198 """ |
|
199 cmd = self.__ftp.currentCommand() |
|
200 if cmd == QFtp.Get: |
|
201 self.__setContent() |
|
202 |
|
203 def __processCommand(self, id, error): |
|
204 """ |
|
205 Private slot to handle the end of FTP commands. |
|
206 |
|
207 @param id id of the command to be processed (integer) (ignored) |
|
208 @param error flag indicating an error condition (boolean) |
|
209 """ |
|
210 if error: |
|
211 if self.__ftp.error() == QFtp.HostNotFound: |
|
212 err = QNetworkReply.HostNotFoundError |
|
213 elif self.__ftp.error() == QFtp.ConnectionRefused: |
|
214 err = QNetworkReply.ConnectionRefusedError |
|
215 else: |
|
216 if self.__loggingIn and \ |
|
217 self.__ftp.state() == QFtp.Connected: |
|
218 # authentication is required |
|
219 if "anonymous" in self.__ftp.errorString(): |
|
220 self.__ftp.login() |
|
221 return |
|
222 |
|
223 newUrl = self.url() |
|
224 auth = QAuthenticator() |
|
225 self.__manager.authenticationRequired.emit(self, auth) |
|
226 if not auth.isNull(): |
|
227 if auth.user(): |
|
228 newUrl.setUserName(auth.user()) |
|
229 newUrl.setPassword(auth.password()) |
|
230 self.setUrl(newUrl) |
|
231 else: |
|
232 auth.setUser("anonymous") |
|
233 auth.setPassword("anonymous") |
|
234 if self.__ftp.state() == QFtp.Connected: |
|
235 self.__ftp.login(auth.user(), auth.password()) |
|
236 return |
|
237 |
|
238 err = QNetworkReply.ProtocolFailure |
|
239 self.setError(err, self.__ftp.errorString()) |
|
240 self.error.emit(err) |
|
241 self.finished.emit() |
|
242 if self.__ftp.state() not in [QFtp.Unconnected, QFtp.Closing]: |
|
243 self.__ftp.close() |
|
244 return |
|
245 |
|
246 cmd = self.__ftp.currentCommand() |
|
247 if cmd == QFtp.ConnectToHost: |
|
248 self.__loggingIn = True |
|
249 self.__ftp.login(self.url().userName(), self.url().password()) |
184 self.__ftp.login(self.url().userName(), self.url().password()) |
250 elif cmd == QFtp.Login: |
185 self.__ftp.retrlines("LIST " + self.url().path(), self.__dirCallback) |
251 self.__loggingIn = False |
|
252 self.__ftp.list(self.url().path()) |
|
253 elif cmd == QFtp.List: |
|
254 if len(self.__items) == 1 and \ |
186 if len(self.__items) == 1 and \ |
255 self.__items[0].isFile(): |
187 self.__items[0].isFile(): |
256 self.__ftp.get(self.url().path()) |
188 self.__setContent() |
|
189 self.__ftp.retrbinary("RETR " + self.url().path(), self.__retrCallback) |
|
190 self.__content.append(512 * b' ') |
|
191 self.readyRead.emit() |
257 else: |
192 else: |
258 self.__setListContent() |
193 self.__setListContent() |
259 elif cmd == QFtp.Get: |
194 self.__ftp.quit() |
260 self.finished.emit() |
195 except ftplib.all_errors as err: |
261 self.__ftp.close() |
196 if isinstance(err, socket.gaierror): |
262 |
197 errCode = QNetworkReply.HostNotFoundError |
263 def __processListInfo(self, urlInfo): |
198 elif isinstance(err, socket.error) and err.errno == errno.ECONNREFUSED: |
264 """ |
199 errCode = QNetworkReply.ConnectionRefusedError |
265 Private slot to process list information from the FTP server. |
200 else: |
266 |
201 errCode = QNetworkReply.ProtocolFailure |
267 @param urlInfo reference to the information object (QUrlInfo) |
202 self.setError(errCode, str(err)) |
268 """ |
203 self.error.emit(errCode) |
269 self.__items.append(QUrlInfo(urlInfo)) |
204 self.finished.emit() |
270 |
205 |
271 def __processData(self): |
206 def __dirCallback(self, line): |
272 """ |
207 """ |
273 Private slot to process data from the FTP server. |
208 Private slot handling the receipt of directory listings. |
274 """ |
209 |
275 self.__content += self.__ftp.readAll() |
210 @param line the received line of the directory listing (string) |
276 self.readyRead.emit() |
211 """ |
|
212 words = line.split(None, 8) |
|
213 if len(words) < 6: |
|
214 # skip short lines |
|
215 return |
|
216 filename = words[-1].lstrip() |
|
217 i = filename.find(" -> ") |
|
218 if i >= 0: |
|
219 filename = filename[:i] |
|
220 infostuff = words[-5:-1] |
|
221 mode = words[0].strip() |
|
222 |
|
223 info = QUrlInfo() |
|
224 # 1. type of item |
|
225 if mode[0] == "d": |
|
226 info.setDir(True) |
|
227 info.setFile(False) |
|
228 elif mode[0] == "l": |
|
229 info.setSymLink(True) |
|
230 elif mode[0] == "-": |
|
231 info.setDir(False) |
|
232 info.setFile(True) |
|
233 # 2. name |
|
234 info.setName(filename.strip()) |
|
235 # 3. size |
|
236 if mode[0] == "-": |
|
237 info.setSize(int(infostuff[0])) |
|
238 # 4. last modified |
|
239 if infostuff[1] in self.Monthnames2Int: |
|
240 month = self.Monthnames2Int[infostuff[1]] |
|
241 else: |
|
242 month = 1 |
|
243 if ":" in infostuff[3]: |
|
244 # year is current year |
|
245 year = QDate.currentDate().year() |
|
246 timeStr = infostuff[3] |
|
247 else: |
|
248 year = int(infostuff[3]) |
|
249 timeStr = "00:00" |
|
250 date = QDate(year, month, int(infostuff[2])) |
|
251 time = QTime.fromString(timeStr, "hh:mm") |
|
252 lastModified = QDateTime(date, time) |
|
253 info.setLastModified(lastModified) |
|
254 |
|
255 self.__items.append(info) |
|
256 |
|
257 QCoreApplication.processEvents() |
|
258 |
|
259 def __retrCallback(self, data): |
|
260 """ |
|
261 Private slot handling the reception of data. |
|
262 |
|
263 @param data data received from the FTP server (bytes) |
|
264 """ |
|
265 self.__content += QByteArray(data) |
|
266 |
|
267 QCoreApplication.processEvents() |
277 |
268 |
278 def __setContent(self): |
269 def __setContent(self): |
279 """ |
270 """ |
280 Private method to finish the setup of the data. |
271 Private method to finish the setup of the data. |
281 """ |
272 """ |
399 self.trUtf8("Listing of {0}").format(basePath), |
390 self.trUtf8("Listing of {0}").format(basePath), |
400 parentStr, |
391 parentStr, |
401 table |
392 table |
402 ) |
393 ) |
403 self.__content = QByteArray(content.encode("utf8")) |
394 self.__content = QByteArray(content.encode("utf8")) |
|
395 self.__content.append(512 * b' ') |
404 |
396 |
405 self.open(QIODevice.ReadOnly | QIODevice.Unbuffered) |
397 self.open(QIODevice.ReadOnly | QIODevice.Unbuffered) |
406 self.setHeader(QNetworkRequest.ContentTypeHeader, "text/html; charset=UTF-8") |
398 self.setHeader(QNetworkRequest.ContentTypeHeader, "text/html; charset=UTF-8") |
407 self.setHeader(QNetworkRequest.ContentLengthHeader, self.__content.size()) |
399 self.setHeader(QNetworkRequest.ContentLengthHeader, self.__content.size()) |
408 self.setAttribute(QNetworkRequest.HttpStatusCodeAttribute, 200) |
400 self.setAttribute(QNetworkRequest.HttpStatusCodeAttribute, 200) |
409 self.setAttribute(QNetworkRequest.HttpReasonPhraseAttribute, "Ok") |
401 self.setAttribute(QNetworkRequest.HttpReasonPhraseAttribute, "Ok") |
410 self.metaDataChanged.emit() |
402 self.metaDataChanged.emit() |
411 self.downloadProgress.emit(self.__content.size(), self.__content.size()) |
403 self.downloadProgress.emit(self.__content.size(), self.__content.size()) |
412 self.readyRead.emit() |
404 self.readyRead.emit() |
413 self.finished.emit() |
|
414 self.__ftp.close() |
|