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