Helpviewer/Network/FileReply.py

changeset 2063
2227d4cc905e
child 2080
4b33165dd55b
equal deleted inserted replaced
2062:f275d3afe038 2063:2227d4cc905e
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a network reply class for directory resources.
8 """
9
10 from PyQt4.QtCore import Qt, QByteArray, QTimer, QDir, QIODevice, QUrl, QBuffer
11 from PyQt4.QtGui import QPixmap
12 from PyQt4.QtNetwork import QNetworkReply, QNetworkRequest
13 from PyQt4.QtWebKit import QWebSettings
14
15 import UI.PixmapCache
16
17
18 dirListPage_html = """\
19 <?xml version="1.0" encoding="UTF-8" ?>
20 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
21 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
22 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
23 <head>
24 <title>{0}</title>
25 <style type="text/css">
26 body {{
27 padding: 3em 0em;
28 background: -webkit-gradient(linear, left top, left bottom, from(#85784A), to(#FDFDFD), color-stop(0.5, #FDFDFD));
29 background-repeat: repeat-x;
30 }}
31 #box {{
32 background: white;
33 border: 1px solid #85784A;
34 width: 80%;
35 padding: 30px;
36 margin: auto;
37 -webkit-border-radius: 0.8em;
38 }}
39 h1 {{
40 font-size: 130%;
41 font-weight: bold;
42 border-bottom: 1px solid #85784A;
43 }}
44 th {{
45 background-color: #B8B096;
46 color: black;
47 }}
48 table {{
49 border: solid 1px #85784A;
50 margin: 5px 0;
51 width: 100%;
52 }}
53 tr.odd {{
54 background-color: white;
55 color: black;
56 }}
57 tr.even {{
58 background-color: #CEC9B8;
59 color: black;
60 }}
61 .modified {{
62 text-align: left;
63 vertical-align: top;
64 white-space: nowrap;
65 }}
66 .size {{
67 text-align: right;
68 vertical-align: top;
69 white-space: nowrap;
70 padding-right: 22px;
71 }}
72 .name {{
73 text-align: left;
74 vertical-align: top;
75 white-space: pre-wrap;
76 width: 100%
77 }}
78 {1}
79 </style>
80 </head>
81 <body>
82 <div id="box">
83 <h1>{2}</h1>
84 {3}
85 <table align="center" cellspacing="0" width="90%">
86 {4}
87 </table>
88 </div>
89 </body>
90 </html>
91 """
92
93
94 class FileReply(QNetworkReply):
95 """
96 Class implementing a network reply for directory resources.
97 """
98 def __init__(self, url, parent=None):
99 """
100 Constructor
101
102 @param url requested FTP URL (QUrl)
103 @param parent reference to the parent object (QObject)
104 """
105 super().__init__(parent)
106
107 self.__content = QByteArray()
108 self.__units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
109
110 if url.path() == "":
111 url.setPath("/")
112 self.setUrl(url)
113
114 QTimer.singleShot(0, self.__loadDirectory)
115
116 def abort(self):
117 """
118 Public slot to abort the operation.
119 """
120 # do nothing
121 pass
122
123 def bytesAvailable(self):
124 """
125 Public method to determined the bytes available for being read.
126
127 @return bytes available (integer)
128 """
129 return self.__content.size()
130
131 def isSequential(self):
132 """
133 Public method to check for sequential access.
134
135 @return flag indicating sequential access (boolean)
136 """
137 return True
138
139 def readData(self, maxlen):
140 """
141 Protected method to retrieve data from the reply object.
142
143 @param maxlen maximum number of bytes to read (integer)
144 @return string containing the data (bytes)
145 """
146 if self.__content.size():
147 len_ = min(maxlen, self.__content.size())
148 buffer = bytes(self.__content[:len_])
149 self.__content.remove(0, len_)
150 return buffer
151
152 def __cssLinkClass(self, icon, size=32):
153 """
154 Private method to generate a link class with an icon.
155
156 @param icon icon to be included (QIcon)
157 @param size size of the icon to be generated (integer)
158 @return CSS class string (string)
159 """
160 cssString = \
161 """a.{{0}} {{{{\n"""\
162 """ padding-left: {0}px;\n"""\
163 """ background: transparent url(data:image/png;base64,{1}) no-repeat center left;\n"""\
164 """ font-weight: bold;\n"""\
165 """}}}}\n"""
166 pixmap = icon.pixmap(size, size)
167 imageBuffer = QBuffer()
168 imageBuffer.open(QIODevice.ReadWrite)
169 if not pixmap.save(imageBuffer, "PNG"):
170 # write a blank pixmap on error
171 pixmap = QPixmap(size, size)
172 pixmap.fill(Qt.transparent)
173 imageBuffer.buffer().clear()
174 pixmap.save(imageBuffer, "PNG")
175 return cssString.format(size + 4,
176 str(imageBuffer.buffer().toBase64(), encoding="ascii"))
177
178 def __loadDirectory(self):
179 """
180 Private slot loading the directory and preparing the listing page.
181 """
182 dir = QDir(self.url().toLocalFile())
183 dirItems = dir.entryInfoList(QDir.AllEntries | QDir.Hidden | QDir.NoDotAndDotDot,
184 QDir.Name | QDir.DirsFirst)
185
186 u = self.url()
187 if not u.path().endswith("/"):
188 u.setPath(u.path() + "/")
189
190 baseUrl = self.url().toString()
191 basePath = u.path()
192
193 linkClasses = {}
194 iconSize = QWebSettings.globalSettings().fontSize(QWebSettings.DefaultFontSize)
195
196 parent = u.resolved(QUrl(".."))
197 if parent.isParentOf(u):
198 icon = UI.PixmapCache.getIcon("up.png")
199 linkClasses["link_parent"] = \
200 self.__cssLinkClass(icon, iconSize).format("link_parent")
201 parentStr = self.trUtf8(
202 """ <p><a class="link_parent" href="{0}">"""
203 """Change to parent directory</a></p>"""
204 ).format(parent.toString())
205 else:
206 parentStr = ""
207
208 row = \
209 """ <tr class="{0}">"""\
210 """<td class="name"><a class="{1}" href="{2}">{3}</a></td>"""\
211 """<td class="size">{4}</td>"""\
212 """<td class="modified">{5}</td>"""\
213 """</tr>\n"""
214 table = self.trUtf8(
215 """ <tr>"""
216 """<th align="left">Name</th>"""
217 """<th>Size</th>"""
218 """<th align="left">Last modified</th>"""
219 """</tr>\n"""
220 )
221
222 i = 0
223 for item in dirItems:
224 name = item.fileName()
225 if item.isDir() and not name.endswith("/"):
226 name += "/"
227 child = u.resolved(QUrl(name.replace(":", "%3A")))
228
229 if item.isFile():
230 size = item.size()
231 unit = 0
232 while size:
233 newSize = size // 1024
234 if newSize and unit < len(self.__units):
235 size = newSize
236 unit += 1
237 else:
238 break
239
240 sizeStr = self.trUtf8("{0} {1}", "size unit")\
241 .format(size, self.__units[unit])
242 linkClass = "link_file"
243 if linkClass not in linkClasses:
244 icon = UI.PixmapCache.getIcon("fileMisc.png")
245 linkClasses[linkClass] = \
246 self.__cssLinkClass(icon, iconSize).format(linkClass)
247 else:
248 sizeStr = ""
249 linkClass = "link_dir"
250 if linkClass not in linkClasses:
251 icon = UI.PixmapCache.getIcon("dirClosed.png")
252 linkClasses[linkClass] = \
253 self.__cssLinkClass(icon, iconSize).format(linkClass)
254 table += row.format(
255 i == 0 and "odd" or "even",
256 linkClass,
257 child.toString(),
258 Qt.escape(item.fileName()),
259 sizeStr,
260 item.lastModified().toString("yyyy-MM-dd hh:mm"),
261 )
262 i = 1 - i
263
264 content = dirListPage_html.format(
265 Qt.escape(baseUrl),
266 "".join(linkClasses.values()),
267 self.trUtf8("Listing of {0}").format(basePath),
268 parentStr,
269 table
270 )
271 self.__content = QByteArray(content.encode("utf8"))
272 self.__content.append(512 * b' ')
273
274 self.open(QIODevice.ReadOnly | QIODevice.Unbuffered)
275 self.setHeader(QNetworkRequest.ContentTypeHeader, "text/html; charset=UTF-8")
276 self.setHeader(QNetworkRequest.ContentLengthHeader, self.__content.size())
277 self.setAttribute(QNetworkRequest.HttpStatusCodeAttribute, 200)
278 self.setAttribute(QNetworkRequest.HttpReasonPhraseAttribute, "Ok")
279 self.metaDataChanged.emit()
280 self.downloadProgress.emit(self.__content.size(), self.__content.size())
281 self.readyRead.emit()
282 self.finished.emit()

eric ide

mercurial