Helpviewer/Network/FtpReply.py

changeset 278
c93823b96faa
child 287
52b4c72080d2
equal deleted inserted replaced
276:4a7e25ee406c 278:c93823b96faa
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a network reply class for FTP resources.
8 """
9
10 from PyQt4.QtCore import QByteArray, QIODevice, Qt, QUrl, QTimer, QBuffer
11 from PyQt4.QtGui import QPixmap
12 from PyQt4.QtNetwork import QFtp, QNetworkReply, QNetworkRequest, QUrlInfo
13 from PyQt4.QtWebKit import QWebSettings
14
15 import UI.PixmapCache
16
17 ftpListPage_html = """\
18 <?xml version="1.0" encoding="UTF-8" ?>
19 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
20 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
21 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
22 <head>
23 <title>{0}</title>
24 <style type="text/css">
25 body {{
26 padding: 3em 0em;
27 background: -webkit-gradient(linear, left top, left bottom, from(#85784A), to(#FDFDFD), color-stop(0.5, #FDFDFD));
28 background-repeat: repeat-x;
29 }}
30 #box {{
31 background: white;
32 border: 1px solid #85784A;
33 width: 80%;
34 padding: 30px;
35 margin: auto;
36 -webkit-border-radius: 0.8em;
37 }}
38 h1 {{
39 font-size: 130%;
40 font-weight: bold;
41 border-bottom: 1px solid #85784A;
42 }}
43 th {{
44 background-color: #B8B096;
45 color: black;
46 }}
47 table {{
48 border: solid 1px #85784A;
49 margin: 5px 0;
50 width: 100%;
51 }}
52 tr.odd {{
53 background-color: white;
54 color: black;
55 }}
56 tr.even {{
57 background-color: #CEC9B8;
58 color: black;
59 }}
60 .modified {{
61 text-align: left;
62 vertical-align: top;
63 white-space: nowrap;
64 }}
65 .size {{
66 text-align: right;
67 vertical-align: top;
68 white-space: nowrap;
69 padding-right: 22px;
70 }}
71 .name {{
72 text-align: left;
73 vertical-align: top;
74 white-space: pre-wrap;
75 width: 100%
76 }}
77 {1}
78 </style>
79 </head>
80 <body>
81 <div id="box">
82 <h1>{2}</h1>
83 {3}
84 <table align="center" cellspacing="0" width="90%">
85 {4}
86 </table>
87 </div>
88 </body>
89 </html>
90 """
91
92 class FtpReply(QNetworkReply):
93 """
94 Class implementing a network reply for FTP resources.
95 """
96 def __init__(self, url, parent = None):
97 """
98 Constructor
99
100 @param url requested FTP URL (QUrl)
101 @param parent reference to the parent object (QObject)
102 """
103 QNetworkReply.__init__(self, parent)
104
105 self.__ftp = QFtp(self)
106 self.__ftp.listInfo.connect(self.__processListInfo)
107 self.__ftp.readyRead.connect(self.__processData)
108 self.__ftp.commandFinished.connect(self.__processCommand)
109 self.__ftp.commandStarted.connect(self.__commandStarted)
110 self.__ftp.dataTransferProgress.connect(self.downloadProgress)
111
112 self.__items = []
113 self.__content = QByteArray()
114 self.__units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
115
116 self.setUrl(url)
117 QTimer.singleShot(0, self.__connectToHost)
118
119 def abort(self):
120 """
121 Public slot to abort the operation.
122 """
123 # do nothing
124 pass
125
126 def bytesAvailable(self):
127 """
128 Public method to determined the bytes available for being read.
129
130 @return bytes available (integer)
131 """
132 return self.__content.size()
133
134 def isSequential(self):
135 """
136 Public method to check for sequential access.
137
138 @return flag indicating sequential access (boolean)
139 """
140 return True
141
142 def readData(self, maxlen):
143 """
144 Protected method to retrieve data from the reply object.
145
146 @param maxlen maximum number of bytes to read (integer)
147 @return string containing the data (bytes)
148 """
149 if self.__content.size():
150 len_ = min(maxlen, self.__content.size())
151 buffer = bytes(self.__content[:len_])
152 self.__content.remove(0, len_)
153 return buffer
154
155 def __connectToHost(self):
156 """
157 Private slot to start the FTP process by connecting to the host.
158 """
159 self.__ftp.connectToHost(self.url().host())
160
161 def __commandStarted(self, id):
162 """
163 Private slot to handle the start of FTP commands.
164
165 @param id id of the command to be processed (integer) (ignored)
166 """
167 cmd = self.__ftp.currentCommand()
168 if cmd == QFtp.Get:
169 self.__setContent()
170
171 def __processCommand(self, id, error):
172 """
173 Private slot to handle the end of FTP commands.
174
175 @param id id of the command to be processed (integer) (ignored)
176 @param error flag indicating an error condition (boolean)
177 """
178 if error:
179 if error == QFtp.HostNotFound:
180 err = QNetworkReply.HostNotFoundError
181 elif error == QFtp.ConnectionRefused:
182 err = QNetworkReply.ConnectionRefusedError
183 else:
184 err = QNetworkReply.ContentNotFoundError
185 self.setError(err, self.__ftp.errorString())
186 self.error.emit(err)
187 return
188
189 cmd = self.__ftp.currentCommand()
190 if cmd == QFtp.ConnectToHost:
191 self.__ftp.login()
192 elif cmd == QFtp.Login:
193 self.__ftp.list(self.url().path())
194 elif cmd == QFtp.List:
195 if len(self.__items) == 1 and \
196 self.__items[0].isFile():
197 self.__ftp.get(self.url().path())
198 else:
199 self.__setListContent()
200 elif cmd == QFtp.Get:
201 self.finished.emit()
202 self.__ftp.close()
203
204 def __processListInfo(self, urlInfo):
205 """
206 Private slot to process list information from the FTP server.
207
208 @param urlInfo reference to the information object (QUrlInfo)
209 """
210 self.__items.append(QUrlInfo(urlInfo))
211
212 def __processData(self):
213 """
214 Private slot to process data from the FTP server.
215 """
216 self.__content += self.__ftp.readAll()
217 self.readyRead.emit()
218
219 def __setContent(self):
220 """
221 Private method to set the finish the setup of the data.
222 """
223 self.open(QIODevice.ReadOnly | QIODevice.Unbuffered)
224 self.setHeader(QNetworkRequest.ContentLengthHeader, self.__items[0].size())
225 self.setAttribute(QNetworkRequest.HttpStatusCodeAttribute, 200)
226 self.setAttribute(QNetworkRequest.HttpReasonPhraseAttribute, "Ok")
227 self.metaDataChanged.emit()
228
229 def __cssLinkClass(self, icon, size = 32):
230 """
231 Private method to generate a link class with an icon.
232
233 @param icon icon to be included (QIcon)
234 @param size size of the icon to be generated (integer)
235 @return CSS class string (string)
236 """
237 cssString = \
238 """a.{{0}} {{{{\n"""\
239 """ padding-left: {0}px;\n"""\
240 """ background: transparent url(data:image/png;base64,{1}) no-repeat center left;\n"""\
241 """ font-weight: bold;\n"""\
242 """}}}}\n"""
243 pixmap = icon.pixmap(size, size)
244 imageBuffer = QBuffer()
245 imageBuffer.open(QIODevice.ReadWrite)
246 if not pixmap.save(imageBuffer, "PNG"):
247 # write a blank pixmap on error
248 pixmap = QPixmap(size, size)
249 pixmap.fill(Qt.transparent)
250 imageBuffer.buffer().clear()
251 pixmap.save(imageBuffer, "PNG")
252 return cssString.format(size + 4,
253 str(imageBuffer.buffer().toBase64(), encoding="ascii"))
254
255 def __setListContent(self):
256 """
257 Private method to prepare the content for the reader.
258 """
259 u = self.url()
260 if not u.path().endswith("/"):
261 u.setPath(u.path() + "/")
262
263 baseUrl = self.url().toString()
264 basePath = u.path()
265
266 linkClasses = {}
267 iconSize = QWebSettings.globalSettings().fontSize(QWebSettings.DefaultFontSize);
268
269 parent = u.resolved(QUrl(".."))
270 if parent.isParentOf(u):
271 icon = UI.PixmapCache.getIcon("up.png")
272 linkClasses["link_parent"] = \
273 self.__cssLinkClass(icon, iconSize).format("link_parent")
274 parentStr = self.trUtf8(
275 """ <p><a class="link_parent" href="{0}">"""
276 """Change to parent directory</a></p>"""
277 ).format(parent.toString())
278 else:
279 parentStr = ""
280
281 row = \
282 """ <tr class="{0}">"""\
283 """<td class="name"><a class="{1}" href="{2}">{3}</a></td>"""\
284 """<td class="size">{4}</td>"""\
285 """<td class="modified">{5}</td>"""\
286 """</tr>\n"""
287 table = self.trUtf8(
288 """ <tr>"""
289 """<th align="left">Name</th>"""
290 """<th>Size</th>"""
291 """<th align="left">Last modified</th>"""
292 """</tr>\n"""
293 )
294
295 i = 0
296 for item in self.__items:
297 name = item.name()
298 if item.isDir() and not name.endswith("/"):
299 name += "/"
300 child = u.resolved(QUrl(name.replace(":", "%3A")))
301
302 if item.isFile():
303 size = item.size()
304 unit = 0
305 while size:
306 newSize = size // 1024
307 if newSize and unit < len(self.__units):
308 size = newSize
309 unit += 1
310 else:
311 break
312
313 sizeStr = self.trUtf8("{0} {1}", "size unit")\
314 .format(size, self.__units[unit])
315 linkClass = "link_file"
316 if linkClass not in linkClasses:
317 icon = UI.PixmapCache.getIcon("fileMisc.png")
318 linkClasses[linkClass] = \
319 self.__cssLinkClass(icon, iconSize).format(linkClass)
320 else:
321 sizeStr = ""
322 linkClass = "link_dir"
323 if linkClass not in linkClasses:
324 icon = UI.PixmapCache.getIcon("dirClosed.png")
325 linkClasses[linkClass] = \
326 self.__cssLinkClass(icon, iconSize).format(linkClass)
327 table += row.format(
328 i == 0 and "odd" or "even",
329 linkClass,
330 child.toString(),
331 Qt.escape(item.name()),
332 sizeStr,
333 item.lastModified().toString("yyyy-MM-dd hh:mm"),
334 )
335 i = 1 - i
336
337 content = ftpListPage_html.format(
338 Qt.escape(baseUrl),
339 "".join(linkClasses.values()),
340 self.trUtf8("Listing of {0}").format(basePath),
341 parentStr,
342 table
343 )
344 self.__content = QByteArray(content.encode("utf8"))
345
346 self.open(QIODevice.ReadOnly | QIODevice.Unbuffered)
347 self.setHeader(QNetworkRequest.ContentTypeHeader, "text/html; charset=UTF-8")
348 self.setHeader(QNetworkRequest.ContentLengthHeader, self.__content.size())
349 self.setAttribute(QNetworkRequest.HttpStatusCodeAttribute, 200)
350 self.setAttribute(QNetworkRequest.HttpReasonPhraseAttribute, "Ok")
351 self.metaDataChanged.emit()
352 self.downloadProgress.emit(self.__content.size(), self.__content.size())
353 self.readyRead.emit()
354 self.finished.emit()
355 self.__ftp.close()

eric ide

mercurial