PageRenderTime 237ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/frescobaldi/python/frescobaldi_app/download.py

https://gitlab.com/aguai/lilykde
Python | 340 lines | 289 code | 26 blank | 25 comment | 7 complexity | 18a75f66f577c645cda7b7b6a1ec9eac MD5 | raw file
  1. # This file is part of the Frescobaldi project, http://www.frescobaldi.org/
  2. #
  3. # Copyright (c) 2008, 2009, 2010 by Wilbert Berendsen
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  18. # See http://www.gnu.org/licenses/ for more information.
  19. from __future__ import unicode_literals
  20. """
  21. Dialog to download new binary versions of LilyPond
  22. """
  23. import os, re, shutil
  24. from PyQt4.QtCore import QObject, QProcess, Qt, SIGNAL
  25. from PyQt4.QtGui import QComboBox, QGridLayout, QGroupBox, QLabel, QProgressBar
  26. from PyKDE4.kdecore import KGlobal, KUrl, i18n
  27. from PyKDE4.kdeui import KDialog, KGuiItem, KIcon, KMessageBox
  28. from PyKDE4.kio import KFile, KIO, KUrlRequester
  29. from frescobaldi_app.lilydoc import HtmlLoader
  30. # parse version of a LilyPond package
  31. _version_re = re.compile(r'(\d+(\.\d+)+)(-(\d+))?')
  32. class LilyPondDownloadDialog(KDialog):
  33. def __init__(self, info):
  34. """info is a LilyPondInfoDialog (see settings.py)"""
  35. KDialog.__init__(self, info)
  36. self.info = info
  37. # local attributes
  38. self.job = None
  39. self.unpackJob = None
  40. self.setButtons(KDialog.ButtonCode(
  41. KDialog.Help | KDialog.Details | KDialog.Ok | KDialog.Cancel))
  42. layout = QGridLayout(self.mainWidget())
  43. self.setButtonText(KDialog.Ok, i18n("Install"))
  44. self.setButtonIcon(KDialog.Ok, KIcon("download"))
  45. self.setCaption(i18n("Download LilyPond"))
  46. self.setHelp("download-lilypond")
  47. l = QLabel(i18n(
  48. "With this tool you can download packaged binary releases "
  49. "of LilyPond for your operating system."))
  50. l.setWordWrap(True)
  51. layout.addWidget(l, 0, 0, 1, 2)
  52. v = self.lilyVersion = QComboBox()
  53. v.currentIndexChanged.connect(self.selectVersion, Qt.QueuedConnection)
  54. v.setToolTip(i18n(
  55. "Select the LilyPond version you want to download."))
  56. l = QLabel(i18n("Version:"))
  57. l.setBuddy(v)
  58. layout.addWidget(l, 1, 0)
  59. layout.addWidget(v, 1, 1)
  60. d = self.installDest = KUrlRequester()
  61. d.setMode(KFile.Mode(KFile.Directory | KFile.LocalOnly))
  62. d.setPath(config().readPathEntry(
  63. 'lilypond install path', os.path.expanduser('~/lilypond_bin/')))
  64. d.setToolTip(i18n(
  65. "Select a writable directory you want to install LilyPond to.\n"
  66. "(A version-numbered directory will be created in this directory.)"))
  67. l = QLabel(i18n("Install into:"))
  68. l.setBuddy(d)
  69. layout.addWidget(l, 2, 0)
  70. layout.addWidget(d, 2, 1)
  71. s = self.status = QLabel()
  72. layout.addWidget(s, 3, 0, 1, 2)
  73. p = self.progress = QProgressBar()
  74. layout.addWidget(p, 4, 0, 1, 2)
  75. details = QGroupBox(i18n("Details"))
  76. layout.addWidget(details, 5, 0, 1, 2)
  77. layout = QGridLayout()
  78. details.setLayout(layout)
  79. b = self.baseUrl = QComboBox()
  80. b.setEditable(True)
  81. b.setToolTip(i18n(
  82. "The website where LilyPond binaries can be downloaded."))
  83. b.addItems(['http://download.linuxaudio.org/lilypond/binaries/'])
  84. b.setCurrentIndex(0)
  85. l = QLabel(i18n("Download from:"))
  86. l.setBuddy(b)
  87. layout.addWidget(l, 0, 0)
  88. layout.addWidget(b, 0, 1)
  89. m = self.machineType = QComboBox()
  90. items = [
  91. 'linux-x86',
  92. 'linux-64',
  93. 'linux-ppc',
  94. 'freebsd-x86',
  95. 'freebsd-64',
  96. 'darwin-x86',
  97. 'darwin-ppc',
  98. ]
  99. m.addItems(items)
  100. l = QLabel(i18n("Machine type:"))
  101. l.setBuddy(m)
  102. layout.addWidget(l, 1, 0)
  103. layout.addWidget(m, 1, 1)
  104. u = self.packageUrl = KUrlRequester()
  105. u.setToolTip(i18n(
  106. "This is the URL to the package that will be downloaded and "
  107. "installed.\n"
  108. "You can also browse to other places to select a LilyPond package."))
  109. l = QLabel(i18n("Package Url:"))
  110. l.setBuddy(u)
  111. layout.addWidget(l, 2, 0)
  112. layout.addWidget(u, 2, 1)
  113. self.setDetailsWidget(details)
  114. # default for machine
  115. platform, machine = os.uname()[0::4]
  116. if '64' in machine:
  117. machine = '64'
  118. elif '86' in machine:
  119. machine = 'x86'
  120. elif 'ower' in machine or 'ppc' in machine:
  121. machine = 'ppc'
  122. mtype = platform.lower() + '-' + machine
  123. if mtype in items:
  124. m.setCurrentIndex(items.index(mtype))
  125. else:
  126. self.setDetailsWidgetVisible(True)
  127. m.currentIndexChanged.connect(self.downloadVersions)
  128. self.downloadVersions()
  129. def downloadVersions(self):
  130. directory = self.baseUrl.currentText()
  131. if not directory.endswith('/'):
  132. directory += '/'
  133. directory += self.machineType.currentText()
  134. directory += '/'
  135. self.directory = directory
  136. self.loader = HtmlLoader(directory)
  137. self.status.setText(i18n("Downloading directory listing..."))
  138. self.progress.setRange(0, 0)
  139. self.loader.done.connect(self.versionsDownloaded)
  140. def versionsDownloaded(self):
  141. self.progress.setRange(0, 100)
  142. self.progress.reset()
  143. self.status.setText('')
  144. if self.loader.error():
  145. self.status.setText(i18n(
  146. "No packages found. You can browse to a package manually."))
  147. self.setDetailsWidgetVisible(True)
  148. self.packageUrl.lineEdit().setFocus()
  149. return
  150. versions = {}
  151. versionStrings = {}
  152. for m in re.finditer(r'\bhref="(lilypond-.*?\.sh)"', self.loader.html()):
  153. fileName = m.group(1)
  154. m = _version_re.search(fileName)
  155. if m:
  156. versionStrings[fileName] = m.group()
  157. ver, build = m.group(1, 4)
  158. version = (tuple(map(int, re.findall(r'\d+', ver))), int(build or 0))
  159. versions[fileName] = version
  160. files = versions.keys()
  161. files.sort(key=versions.get)
  162. # add the versions
  163. self.lilyVersion.clear()
  164. self.items = []
  165. # determine last stable and development:
  166. stable, development = False, False
  167. for f in files[::-1]:
  168. if versions[f][0][1] & 1:
  169. if not development:
  170. development = True
  171. self.items.append(f)
  172. self.lilyVersion.addItem(i18n(
  173. "Latest Development Version (%1)", versionStrings[f]))
  174. elif not stable:
  175. stable = True
  176. self.items.append(f)
  177. self.lilyVersion.addItem(i18n(
  178. "Latest Stable Version (%1)", versionStrings[f]))
  179. if stable and development:
  180. break
  181. for f in files:
  182. self.lilyVersion.addItem(versionStrings[f])
  183. self.items.append(f)
  184. self.lilyVersion.setCurrentIndex(0)
  185. def selectVersion(self, index):
  186. self.packageUrl.setUrl(KUrl(self.directory + self.items[index]))
  187. def done(self, result):
  188. if result == KDialog.Accepted:
  189. # Download (OK) clicked
  190. url = self.packageUrl.url()
  191. if not url.isEmpty():
  192. self.enableButtonOk(False)
  193. # save the install path
  194. config().writePathEntry('lilypond install path',
  195. self.installDest.url().path())
  196. if url.isLocalFile():
  197. self.unpack(url.path())
  198. else:
  199. self.download(url)
  200. else:
  201. if self.downloadBusy():
  202. self.cancelDownload()
  203. elif not self.unpackBusy():
  204. KDialog.done(self, result)
  205. def download(self, url):
  206. """Download the package from given KUrl."""
  207. self.progress.setRange(0, 100)
  208. self.status.setText(i18n("Downloading %1...", url.fileName()))
  209. dest = KGlobal.dirs().saveLocation('tmp')
  210. self.job = KIO.copy(url, KUrl(dest),
  211. KIO.JobFlags(KIO.Overwrite | KIO.Resume | KIO.HideProgressInfo))
  212. QObject.connect(self.job, SIGNAL("percent(KJob*, unsigned long)"), self.slotPercent)
  213. QObject.connect(self.job, SIGNAL("result(KJob*)"), self.slotResult, Qt.QueuedConnection)
  214. self.job.start()
  215. def downloadBusy(self):
  216. return bool(self.job)
  217. def cancelDownload(self):
  218. self.job.kill()
  219. self.status.setText(i18n("Download cancelled."))
  220. self.enableButtonOk(True)
  221. self.progress.setValue(0)
  222. def slotPercent(self, job, percent):
  223. self.progress.setValue(percent)
  224. def slotResult(self):
  225. if self.job.error():
  226. self.status.setText(i18n("Download failed: %1", self.job.errorString()))
  227. self.enableButtonOk(True)
  228. else:
  229. fileName = self.job.srcUrls()[0].fileName()
  230. package = os.path.join(self.job.destUrl().path(), fileName)
  231. self.unpack(package)
  232. self.job = None
  233. def unpack(self, package):
  234. """Unpack the given lilypond .sh archive."""
  235. fileName = os.path.basename(package)
  236. ver = version(fileName) or 'unknown' # should not happen
  237. self.prefix = os.path.join(self.installDest.url().path(), ver)
  238. self.lilypond = os.path.join(self.prefix, "bin", "lilypond")
  239. if not os.path.exists(self.prefix):
  240. os.makedirs(self.prefix)
  241. elif os.path.exists(self.lilypond):
  242. result = KMessageBox.questionYesNoCancel(self, i18n(
  243. "LilyPond %1 seems already to be installed in %2.\n\n"
  244. "Do you want to use it or to remove and re-install?",
  245. ver, self.prefix), None,
  246. KGuiItem(i18n("Use existing LilyPond")),
  247. KGuiItem(i18n("Remove and re-install")))
  248. if result == KMessageBox.Yes:
  249. self.info.lilypond.setText(self.lilypond)
  250. self.enableButtonOk(True)
  251. KDialog.done(self, KDialog.Accepted)
  252. return
  253. elif result == KMessageBox.No:
  254. shutil.rmtree(self.prefix, True)
  255. else: # Cancel
  256. self.progress.reset()
  257. self.enableButtonOk(True)
  258. return
  259. self.status.setText(i18n("Unpacking %1...", fileName))
  260. self.progress.setRange(0, 0)
  261. unpack = self.unpackJob = QProcess()
  262. unpack.setProcessChannelMode(QProcess.MergedChannels)
  263. unpack.setWorkingDirectory(self.prefix)
  264. unpack.finished.connect(self.unpackFinished)
  265. unpack.error.connect(self.unpackError)
  266. unpack.start("sh", [package, "--batch", "--prefix", self.prefix])
  267. def unpackBusy(self):
  268. return bool(self.unpackJob and self.unpackJob.state())
  269. def unpackFinished(self, exitCode, exitStatus):
  270. self.progress.setRange(0, 100)
  271. self.progress.reset()
  272. self.enableButtonOk(True)
  273. if exitStatus == QProcess.NormalExit and exitCode == 0:
  274. self.status.setText(i18n("Unpacking finished."))
  275. self.info.lilypond.setText(self.lilypond)
  276. KDialog.done(self, KDialog.Accepted)
  277. else:
  278. self.status.setText(i18n("Unpacking failed."))
  279. KMessageBox.error(self, i18n("An error occurred:\n\n%1",
  280. str(self.unpackJob.readAllStandardOutput())))
  281. def unpackError(self, err):
  282. self.progress.setRange(0, 100)
  283. self.progress.reset()
  284. self.enableButtonOk(True)
  285. self.status.setText(i18n("Unpacking failed."))
  286. KMessageBox.error(self, i18n("An error occurred:\n\n%1",
  287. self.unpackJob.errorString()))
  288. def config(group="installertools"):
  289. return KGlobal.config().group(group)
  290. def version(fileName):
  291. """Determine version of the given package filename."""
  292. m = _version_re.search(fileName)
  293. if m:
  294. return m.group()