Package pytsonui :: Module repository
[hide private]
[frames] | no frames]

Source Code for Module pytsonui.repository

  1  from . import ts3print, setupUi 
  2  from .dialogs import MultiInputDialog 
  3   
  4  import ts3defines 
  5  import pytson 
  6  import devtools 
  7   
  8  import json 
  9  import traceback 
 10  import io 
 11   
 12  from PythonQt.QtCore import Qt, QUrl 
 13  from PythonQt.QtGui import (QDialog, QMovie, QListWidgetItem, QMessageBox, 
 14                              QInputDialog, QLineEdit) 
 15  from PythonQt.QtNetwork import (QNetworkAccessManager, QNetworkRequest, 
 16                                  QNetworkReply) 
 17  from PythonQt import BoolResult 
 18   
 19   
20 -class RepositoryDialog(QDialog, pytson.Translatable):
21 master_url = QUrl("https://raw.githubusercontent.com/pathmann/pyTSon_repository/master/repositorymaster.json") 22
23 - def __init__(self, host, parent=None):
24 super(QDialog, self).__init__(parent) 25 self.setAttribute(Qt.WA_DeleteOnClose) 26 27 self.host = host 28 29 self.pending = 0 30 31 try: 32 with open(pytson.getConfigPath("repositorymaster.json"), "r") as f: 33 repmaster = json.loads(f.read()) 34 self.replist = {x["name"]: x for x in repmaster} 35 except: 36 ts3print(self._tr("Error opening repositorymaster"), 37 ts3defines.LogLevel.LogLevel_ERROR, 38 "pyTSon.RepositoryDialog", 0) 39 raise Exception("Error opening repositorymaster") 40 41 try: 42 setupUi(self, pytson.getPluginPath("ressources", "repository.ui")) 43 self.updateMasterlist() 44 45 movie = QMovie(pytson.getPluginPath("ressources", "loading.gif"), 46 "", self) 47 movie.start() 48 self.loadingLabel.setMovie(movie) 49 50 self.masterloadingLabel.setMovie(movie) 51 self.masterloadingLabel.hide() 52 53 self.nwm = QNetworkAccessManager(self) 54 self.nwm.connect("finished(QNetworkReply*)", self.onNetworkReply) 55 56 self.updateRepositories() 57 58 self.connect("finished(int)", self.onClosed) 59 except Exception as e: 60 self.delete() 61 raise e
62
63 - def onClosed(self):
64 with open(pytson.getConfigPath("repositorymaster.json"), "w") as f: 65 json.dump(list(self.replist.values()), f)
66
67 - def updatePendingButtons(self):
68 if self.pending == 0: 69 self.reloadButton.setEnabled(True) 70 self.updateButton.setEnabled(True) 71 self.loadingLabel.hide() 72 self.masterloadingLabel.hide() 73 else: 74 self.reloadButton.setEnabled(False) 75 self.updateButton.setEnabled(False) 76 self.loadingLabel.show() 77 self.masterloadingLabel.show()
78
79 - def updateRepositories(self):
80 self.addons = {} 81 self.pluginsList.clear() 82 83 self.pending += sum(x["active"] for x in self.replist.values()) 84 for rep in self.replist.values(): 85 if all(x in rep for x in ['name', 'url', 'origin', 'active']): 86 if rep["active"]: 87 self.nwm.get(QNetworkRequest(QUrl(rep["url"]))) 88 else: 89 self.pending -= 1 90 ts3print(self._tr("Invalid repository in list, ignoring"), 91 ts3defines.LogLevel.LogLevel_WARNING, 92 "pyTSon.RepositoryDialog.updateRepositories", 0) 93 94 self.updatePendingButtons()
95
96 - def updateMaster(self):
97 self.pending += 1 98 99 self.masterloadingLabel.show() 100 self.updateButton.setEnabled(False) 101 102 self.nwm.get(QNetworkRequest(self.master_url)) 103 self.updatePendingButtons()
104
105 - def handleMasterReply(self, reply):
106 if reply.error() == QNetworkReply.NoError: 107 try: 108 repos = json.loads(reply.readAll().data().decode('utf-8')) 109 110 for r in repos: 111 if all(x in r for x in ["name", "url", "active", 112 "origin"]): 113 self.addRepository(r) 114 else: 115 ts3print(self._tr("Invalid entry in repositorymaster"), 116 ts3defines.LogLevel.LogLevel_WARNING, 117 "pyTSon.RepositoryManager.onNetworkReply", 118 0) 119 except: 120 ts3print(self._tr("Error reading repositorymaster: {trace}"). 121 format(trace=traceback.format_exc()), 122 ts3defines.LogLevel.LogLevel_ERROR, 123 "pyTSon.RepositoryManager.onNetworkReply", 0) 124 125 self.updateMasterlist()
126
127 - def updateAddons(self, repo, addons):
128 for a in addons: 129 if all(x in a for x in ["name", "author", "version", 130 "apiVersion", "description", 131 "url"]): 132 a["repository"] = repo["name"] 133 if not a["name"] in self.addons: 134 self.addons[a["name"]] = a 135 else: 136 ts3print(self._tr("Invalid entry in repository {name}: " 137 "{repo}").format(name=repo["name"], 138 repo=str(a)), ts3defines.LogLevel.LogLevel_WARNING, 139 "pyTSon.RepositoryDialog.onNetworkReply", 0) 140 break
141
142 - def handleRepositoryReply(self, reply):
143 repo = None 144 for r in self.replist.values(): 145 if reply.url().url() == r["url"]: 146 repo = r 147 break 148 149 if not repo: 150 ts3print(self._tr("Error matching answer from {url} to a " 151 "repository").format(url=reply.url().url()), 152 ts3defines.LogLevel.LogLevel_ERROR, 153 "pyTSon.RepositoryDialog.onNetworkReply", 0) 154 elif reply.error() == QNetworkReply.NoError: 155 try: 156 self.updateAddons(repo, json.loads(reply.readAll().data() 157 .decode('utf-8'))) 158 except ValueError as e: 159 ts3print(self._tr("Error parsing repository {name}: " 160 "{exception}").format(name=repo["name"], 161 exception=str(e)), 162 ts3defines.LogLevel.LogLevel_WARNING, 163 "pyTSon.RepositoryDialog.onNetworkReply", 0) 164 else: 165 ts3print(self._tr("Network error updating repository {name}: " 166 "{error}").format(name=repo["name"], 167 error=reply.error()), 168 ts3defines.LogLevel.LogLevel_WARNING, 169 "pyTSon.RepositoryDialog.onNetworkReply", 0) 170 171 if self.pending == 0: 172 self.updateAddonlist()
173
174 - def onNetworkReply(self, reply):
175 self.pending -= 1 176 177 if reply.url() == self.master_url: 178 self.handleMasterReply(reply) 179 else: 180 self.handleRepositoryReply(reply) 181 182 reply.deleteLater() 183 184 self.updatePendingButtons()
185
186 - def addRepository(self, r):
187 name = r["name"] 188 if name in self.replist: 189 if self.replist[name]["origin"] == "online": 190 if self.replist[name]["url"] != r["url"]: 191 self.replist[name]["url"] = r["url"] 192 ts3print(self._tr("Url for repository {name} updated"). 193 format(name=name), 194 ts3defines.LogLevel.LogLevel_INFO, 195 "pyTSon.RepositoryManager.addRepository", 0) 196 else: 197 ts3print(self._tr("Ignoring online repository {name}, got a " 198 "local one with that name"). 199 format(name=name), ts3defines.LogLevel.LogLevel_INFO, 200 "pyTSon.RepositoryManager.addRepository", 0) 201 else: 202 self.replist[name] = r
203
204 - def updateMasterlist(self):
205 self.repositoryList.clear() 206 207 for name, r in self.replist.items(): 208 item = QListWidgetItem(name) 209 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | 210 Qt.ItemIsEnabled) 211 item.setCheckState(Qt.Checked if r["active"] else Qt.Unchecked) 212 item.setData(Qt.UserRole, name) 213 self.repositoryList.addItem(item)
214
215 - def updateAddonlist(self):
216 if self.pluginsList.currentItem(): 217 cur = self.pluginsList.currentItem().text() 218 else: 219 cur = None 220 221 self.pluginsList.clear() 222 223 for a in self.addons.values(): 224 if (self.replist[a["repository"]]["active"] and 225 ("platforms" not in a or 226 pytson.platformstr() in a["platforms"])): 227 item = QListWidgetItem(a["name"], self.pluginsList) 228 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | 229 Qt.ItemIsEnabled) 230 item.setData(Qt.UserRole, a["name"]) 231 232 if a["name"] in self.host.plugins: 233 if a["version"] > self.host.plugins[a["name"]].version: 234 item.setForeground(Qt.red) 235 item.setToolTip(self._tr("Update available")) 236 elif a["version"] == self.host.plugins[a["name"]].version: 237 item.setForeground(Qt.green) 238 item.setToolTip(self._tr("You have this plugin " 239 "installed, no update " 240 "available")) 241 elif a["version"] < self.host.plugins[a["name"]].version: 242 item.setForeground(Qt.gray) 243 item.setToolTip(self._tr("Your local version has a " 244 "greater version number")) 245 246 if cur and a["name"] == cur: 247 self.pluginsList.setCurrentItem(item) 248 249 self.pluginsList.sortItems()
250
251 - def on_updateButton_clicked(self):
252 self.updateMaster()
253
254 - def on_addButton_clicked(self):
255 (res, name, url) = MultiInputDialog.getTexts(self._tr("Add repository"), 256 self._tr("Name:"), 257 self._tr("URL:"), "", "", 258 self) 259 260 if res: 261 qurl = QUrl(url) 262 if qurl.isValid() and not qurl.isLocalFile(): 263 rep = dict() 264 rep["name"] = name 265 rep["url"] = url 266 rep["origin"] = "local" 267 rep["active"] = True 268 269 self.replist[name] = rep 270 271 item = QListWidgetItem(name) 272 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | 273 Qt.ItemIsEnabled) 274 item.setCheckState(Qt.Checked) 275 item.setData(Qt.UserRole, name) 276 self.repositoryList.addItem(item) 277 else: 278 QMessageBox.critical(self._tr("Error"), 279 self._tr("The URL {url} is not valid"). 280 format(url=url))
281
282 - def on_deleteButton_clicked(self):
283 cur = self.repositoryList.currentItem() 284 if cur: 285 name = cur.data(Qt.UserRole) 286 if name not in self.replist: 287 QMessageBox.critical(self._tr("Internal error"), 288 self._tr("Can't find repository {name} " 289 "in list").format(name=name)) 290 return 291 292 del self.replist[name] 293 cur.delete()
294
295 - def on_repositoryList_doubleClicked(self, item):
296 name = item.data(Qt.UserRole) 297 try: 298 rep = self.replist[name] 299 except: 300 QMessageBox.critical(self, self._tr("Internal error"), 301 self._tr("Can't find repository {name} in " 302 "list").format(name=name)) 303 return 304 305 ok = BoolResult() 306 newurl = QInputDialog.getText(self, 307 self._tr("Change url of repository " 308 "{name}").format(name=name), 309 self._tr("Url:"), QLineEdit.Normal, 310 rep["url"], ok) 311 312 if ok: 313 rep["url"] = newurl 314 rep["origin"] = "local"
315
316 - def on_repositoryList_currentItemChanged(self, cur, prev):
317 if cur: 318 name = cur.data(Qt.UserRole) 319 if name not in self.replist: 320 self.deleteButton.setEnabled(False) 321 QMessageBox.critical(self, self._tr("Internal error"), 322 self._tr("Can't find repository {name} " 323 "in list").format(name=name)) 324 return 325 326 self.deleteButton.setEnabled(True) 327 else: 328 self.deleteButton.setEnabled(False)
329
330 - def on_repositoryList_itemChanged(self, item):
331 if not item: 332 return 333 334 name = item.data(Qt.UserRole) 335 336 if name not in self.replist: 337 QMessageBox.critical(self, self._tr("Internal error"), 338 self._tr("Can't find repository {name} in " 339 "list").format(name=name)) 340 return 341 if self.replist[name]["active"] != (item.checkState() == Qt.Checked): 342 self.replist[name]["active"] = (item.checkState() == Qt.Checked) 343 self.updateAddonlist()
344
345 - def on_pluginsList_currentItemChanged(self, cur, prev):
346 if cur: 347 name = cur.data(Qt.UserRole) 348 if name not in self.addons: 349 QMessageBox.critical(self, self._tr("Internal error"), 350 self._tr("Can't find addon {name} in " 351 "list").format(name=name)) 352 return 353 354 p = self.addons[name] 355 self.nameEdit.setText(p["name"]) 356 self.authorEdit.setText(p["author"]) 357 self.versionEdit.setText(p["version"]) 358 self.descriptionEdit.setPlainText(p["description"]) 359 self.apiEdit.setText(p["apiVersion"]) 360 self.repositoryEdit.setText(p["repository"]) 361 362 if name in self.host.plugins: 363 if p["version"] > self.host.plugins[name].version: 364 self.installButton.setEnabled(True) 365 self.installButton.setText(self._tr("Update")) 366 else: 367 self.installButton.setEnabled(False) 368 self.installButton.setText(self._tr("Install")) 369 else: 370 self.installButton.setEnabled(True) 371 self.installButton.setText(self._tr("Install")) 372 else: 373 self.nameEdit.clear() 374 self.authorEdit.clear() 375 self.versionEdit.clear() 376 self.descriptionEdit.clear() 377 self.apiEdit.clear() 378 self.repositoryEdit.clear()
379
380 - def on_reloadButton_clicked(self):
381 self.updateRepositories()
382
383 - def on_installButton_clicked(self):
384 item = self.pluginsList.currentItem() 385 if not item: 386 return 387 388 name = item.data(Qt.UserRole) 389 if name not in self.addons: 390 QMessageBox.critical(self, self._tr("Internal error"), 391 self._tr("Can't find addon {name} in list"). 392 format(name=name)) 393 return 394 395 p = self.addons[name] 396 397 # update?, so remove the local one first 398 if name in self.host.plugins: 399 if p["version"] > self.host.plugins[name].version: 400 devtools.PluginInstaller.removePlugin(name) 401 else: 402 # should not happen (ui restricted) 403 QMessageBox.critical(self, self._tr("Internal error"), 404 self._tr("This plugin is already " 405 "installed")) 406 return 407 408 self.installer = InstallDialog(self.host, self) 409 self.installer.show() 410 self.installer.install(p)
411 412
413 -class InstallDialog(QDialog, pytson.Translatable):
414 - def __init__(self, host, parent=None):
415 super(QDialog, self).__init__(parent) 416 self.setAttribute(Qt.WA_DeleteOnClose) 417 self.setModal(True) 418 419 self.host = host 420 self.addon = None 421 422 try: 423 setupUi(self, pytson.getPluginPath("ressources", "installer.ui")) 424 425 self.pip = devtools.PluginInstaller(self.consoleEdit.append) 426 self.nwm = QNetworkAccessManager(self) 427 self.nwm.connect("finished(QNetworkReply*)", self.onNetworkReply) 428 429 self.closeFrame.hide() 430 except Exception as e: 431 self.delete() 432 raise e
433
434 - def install(self, addon):
435 self.addon = addon 436 437 req = QNetworkRequest(QUrl(addon["url"])) 438 req.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) 439 self.nwm.get(req) 440 self.consoleEdit.append("Downloading %s ..." % addon["url"])
441
442 - def installPackage(self, pkgstr):
443 self.pip.installPackages([pkgstr]) 444 self.closeFrame.show()
445
446 - def onNetworkReply(self, reply):
447 if reply.error() == QNetworkReply.NoError: 448 self.consoleEdit.append("Download finished.") 449 450 try: 451 if ("application/zip" not in 452 reply.header(QNetworkRequest.ContentTypeHeader)): 453 self.pip.installPlugin(self.addon, 454 reply.readAll().data(). 455 decode('utf-8')) 456 else: 457 self.pip.installPlugin(self.addon, 458 io.BytesIO(reply.readAll().data())) 459 except Exception as e: 460 self.consoleEdit.append("Exception during installation: %s" % 461 e) 462 else: 463 self.consoleEdit.append("Network error: %s" % reply.error()) 464 465 reply.deleteLater() 466 467 self.closeFrame.show()
468
469 - def on_closeButton_clicked(self):
470 self.accept()
471