1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2015 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog to show details about a package. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 try: |
|
12 basestring # __IGNORE_WARNING__ |
|
13 except NameError: |
|
14 basestring = str |
|
15 |
|
16 from PyQt5.QtCore import Qt, QLocale |
|
17 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QTreeWidgetItem, \ |
|
18 QLabel, QHeaderView |
|
19 |
|
20 from .Ui_PipPackageDetailsDialog import Ui_PipPackageDetailsDialog |
|
21 |
|
22 |
|
23 class PipPackageDetailsDialog(QDialog, Ui_PipPackageDetailsDialog): |
|
24 """ |
|
25 Class implementing a dialog to show details about a package. |
|
26 """ |
|
27 def __init__(self, detailsData, downloadsData, parent=None): |
|
28 """ |
|
29 Constructor |
|
30 |
|
31 @param detailsData package details |
|
32 @type dict |
|
33 @param downloadsData downloads information |
|
34 @type dict |
|
35 @param parent reference to the parent widget |
|
36 @type QWidget |
|
37 """ |
|
38 super(PipPackageDetailsDialog, self).__init__(parent) |
|
39 self.setupUi(self) |
|
40 self.setWindowFlags(Qt.Window) |
|
41 |
|
42 self.__locale = QLocale() |
|
43 self.__packageTypeMap = { |
|
44 "sdist": self.tr("Source"), |
|
45 "bdist_wheel": self.tr("Python Wheel"), |
|
46 "bdist_egg": self.tr("Python Egg"), |
|
47 "bdist_wininst": self.tr("MS Windows Installer"), |
|
48 "bdist_msi": self.tr("MS Windows Installer"), |
|
49 "bdist_rpm": self.tr("Unix Installer"), |
|
50 "bdist_deb": self.tr("Unix Installer"), |
|
51 "bdist_dumb": self.tr("Archive"), |
|
52 } |
|
53 |
|
54 self.__populateDetails(detailsData) |
|
55 self.__populateDownloadUrls(downloadsData) |
|
56 self.__populateRequiresProvides(detailsData) |
|
57 |
|
58 def __populateDetails(self, detailsData): |
|
59 """ |
|
60 Private method to populate the details tab. |
|
61 |
|
62 @param detailsData package details |
|
63 @type dict |
|
64 """ |
|
65 self.packageNameLabel.setText( |
|
66 "<h1>{0} {1}</h1".format(self.__sanitize(detailsData["name"]), |
|
67 self.__sanitize(detailsData["version"]))) |
|
68 self.summaryLabel.setText( |
|
69 self.__sanitize(detailsData["summary"][:240])) |
|
70 self.descriptionEdit.setPlainText( |
|
71 self.__sanitize(detailsData["description"])) |
|
72 self.authorLabel.setText(self.__sanitize(detailsData["author"])) |
|
73 self.authorEmailLabel.setText( |
|
74 '<a href="mailto:{0}">{0}</a>'.format( |
|
75 self.__sanitize(detailsData["author_email"]))) |
|
76 self.licenseLabel.setText(self.__sanitize(detailsData["license"])) |
|
77 self.platformLabel.setText(self.__sanitize(detailsData["platform"])) |
|
78 self.homePageLabel.setText( |
|
79 '<a href="{0}">{0}</a>'.format( |
|
80 self.__sanitize(detailsData["home_page"], forUrl=True))) |
|
81 self.packageUrlLabel.setText( |
|
82 '<a href="{0}">{0}</a>'.format( |
|
83 self.__sanitize(detailsData["package_url"], forUrl=True))) |
|
84 self.releaseUrlLabel.setText( |
|
85 '<a href="{0}">{0}</a>'.format( |
|
86 self.__sanitize(detailsData["release_url"], forUrl=True))) |
|
87 self.docsUrlLabel.setText( |
|
88 '<a href="{0}">{0}</a>'.format( |
|
89 self.__sanitize(detailsData["docs_url"], forUrl=True))) |
|
90 self.downloadsDayLabel.setText(self.__locale.toString( |
|
91 detailsData["downloads"]["last_day"])) |
|
92 self.downloadsWeekLabel.setText(self.__locale.toString( |
|
93 detailsData["downloads"]["last_week"])) |
|
94 self.downloadsMonthLabel.setText(self.__locale.toString( |
|
95 detailsData["downloads"]["last_month"])) |
|
96 self.classifiersList.addItems(detailsData["classifiers"]) |
|
97 |
|
98 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) |
|
99 self.buttonBox.button(QDialogButtonBox.Close).setFocus( |
|
100 Qt.OtherFocusReason) |
|
101 |
|
102 def __populateDownloadUrls(self, downloadsData): |
|
103 """ |
|
104 Private method to populate the download URLs tab. |
|
105 |
|
106 @param downloadsData downloads information |
|
107 @type dict |
|
108 """ |
|
109 index = self.infoWidget.indexOf(self.urls) |
|
110 if downloadsData: |
|
111 self.infoWidget.setTabEnabled(index, True) |
|
112 for download in downloadsData: |
|
113 itm = QTreeWidgetItem(self.downloadUrlsList, [ |
|
114 "", |
|
115 self.__packageTypeMap[download["packagetype"]] |
|
116 if download["packagetype"] in self.__packageTypeMap |
|
117 else "", |
|
118 download["python_version"] |
|
119 if download["python_version"] != "source" |
|
120 else "", |
|
121 self.__locale.toString(download["downloads"]), |
|
122 self.__formatUploadDate(download["upload_time"]), |
|
123 self.__formatSize(download["size"]), |
|
124 ]) |
|
125 if download["has_sig"]: |
|
126 pgpLink = ' (<a href="{0}">pgp</a>)'.format( |
|
127 download["url"] + ".asc") |
|
128 else: |
|
129 pgpLink = "" |
|
130 urlLabel = QLabel('<a href="{0}#md5={2}">{1}</a>{3}'.format( |
|
131 download["url"], download["filename"], |
|
132 download["md5_digest"], pgpLink)) |
|
133 urlLabel.setTextInteractionFlags(Qt.LinksAccessibleByMouse) |
|
134 urlLabel.setOpenExternalLinks(True) |
|
135 self.downloadUrlsList.setItemWidget(itm, 0, urlLabel) |
|
136 header = self.downloadUrlsList.header() |
|
137 header.resizeSections(QHeaderView.ResizeToContents) |
|
138 else: |
|
139 self.infoWidget.setTabEnabled(index, False) |
|
140 |
|
141 def __populateRequiresProvides(self, detailsData): |
|
142 """ |
|
143 Private method to populate the requires/provides tab. |
|
144 |
|
145 @param detailsData package details |
|
146 @type dict |
|
147 """ |
|
148 populatedItems = 0 |
|
149 |
|
150 if "requires" in detailsData: |
|
151 self.requiredPackagesList.addItems(detailsData["requires"]) |
|
152 populatedItems += len(detailsData["requires"]) |
|
153 if "requires_dist" in detailsData: |
|
154 self.requiredDistributionsList.addItems( |
|
155 detailsData["requires_dist"]) |
|
156 populatedItems += len(detailsData["requires_dist"]) |
|
157 if "provides" in detailsData: |
|
158 self.providedPackagesList.addItems(detailsData["provides"]) |
|
159 populatedItems += len(detailsData["provides"]) |
|
160 if "provides_dist" in detailsData: |
|
161 self.providedDistributionsList.addItems( |
|
162 detailsData["provides_dist"]) |
|
163 populatedItems += len(detailsData["provides_dist"]) |
|
164 |
|
165 index = self.infoWidget.indexOf(self.requires) |
|
166 self.infoWidget.setTabEnabled(index, populatedItems > 0) |
|
167 |
|
168 def __sanitize(self, text, forUrl=False): |
|
169 """ |
|
170 Private method to clean-up the given text. |
|
171 |
|
172 @param text raw text |
|
173 @type str |
|
174 @param forUrl flag indicating to sanitize an URL text |
|
175 @type bool |
|
176 @return processed text |
|
177 @rtype str |
|
178 """ |
|
179 if text == "UNKNOWN": |
|
180 text = "" |
|
181 elif text == "any": |
|
182 text = self.tr("any") |
|
183 elif text is None: |
|
184 text = "" |
|
185 if forUrl: |
|
186 if not isinstance(text, basestring) or \ |
|
187 not text.startswith(("http://", "https://", "ftp://")): |
|
188 # ignore if the schema is not one of the listed ones |
|
189 text = "" |
|
190 |
|
191 return text |
|
192 |
|
193 def __formatUploadDate(self, datetime): |
|
194 """ |
|
195 Private method to format the upload date. |
|
196 |
|
197 @param datetime upload date and time |
|
198 @type xmlrpc.DateTime or str |
|
199 @return formatted date string |
|
200 @rtype str |
|
201 """ |
|
202 if isinstance(datetime, str): |
|
203 return datetime.split("T")[0] |
|
204 else: |
|
205 date = datetime.value.split("T")[0] |
|
206 return "{0}-{1}-{2}".format(date[:4], date[4:6], date[6:]) |
|
207 |
|
208 def __formatSize(self, size): |
|
209 """ |
|
210 Private slot to format the size. |
|
211 |
|
212 @param size size to be formatted |
|
213 @type int |
|
214 @return formatted size |
|
215 @rtype str |
|
216 """ |
|
217 unit = "" |
|
218 if size < 1024: |
|
219 unit = self.tr("B") |
|
220 elif size < 1024 * 1024: |
|
221 size /= 1024 |
|
222 unit = self.tr("KB") |
|
223 elif size < 1024 * 1024 * 1024: |
|
224 size /= 1024 * 1024 |
|
225 unit = self.tr("MB") |
|
226 else: |
|
227 size /= 1024 * 1024 * 1024 |
|
228 unit = self.tr("GB") |
|
229 return self.tr("{0:.1f} {1}", "value, unit").format(size, unit) |
|