Skip to content

Instantly share code, notes, and snippets.

@snim2
Created April 20, 2011 20:01
Show Gist options
  • Save snim2/932577 to your computer and use it in GitHub Desktop.
Save snim2/932577 to your computer and use it in GitHub Desktop.
Automatically update an app from a remote Mercurial repository
#!/usr/bin/env python
"""
Basic app which can be updated directly from a Mercurial repository.
Copyright (C) Sarah Mount, 2011.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import logging
import os
import shutil
import stat
import sys
import tempfile
import traceback
from PyQt4 import QtGui, QtCore
from mercurial import commands, hg, ui, error
REPO_DIR = os.path.expanduser('~' + os.sep + '.myrepo')
URL = 'https://MYPROJECT.googlecode.com/hg/'
MYAPPLOGGER = 'WHATEVER'
__author__ = 'Sarah Mount'
__date__ = 'April 2011'
__credits__ = 'Matt Mackall, Steve Borho, Mercurial mailing list'
class AppUpdator(QtCore.QThread):
"""This class automatically updates a PyQt app from a remote
Mercurial repository.
"""
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
self.ui = ui.ui()
self.logger = logging.getLogger(MYAPPLOGGER)
self.info = lambda msg : self.logger.info(msg)
self.debug = lambda msg : self.logger.debug(msg)
self.ui = ui.ui()
self.url = 'https://open-ihm.googlecode.com/hg/'
try:
self.repo = hg.repository(self.ui, REPO_DIR)
except Exception:
self.repo = hg.repository(self.ui, REPO_DIR, create=True)
return
def run(self):
# Redirect stdin and stdout to tempfiles.
# This fixes a Windows bug which causes a Bad File Descriptor error.
sys.stdout = tempfile.TemporaryFile()
sys.stderr = tempfile.TemporaryFile()
try:
self.pullAndMerge()
except Exception:
self.fail()
return
try:
self.install()
except Exception:
self.fail()
return
self.emit(QtCore.SIGNAL("updateSuccess()"))
return
def chmod(self):
"""Fix a Windows bug which marks files / folders in REPO_DIR read-only.
"""
if not (sys.platform == 'win32' or sys.platform == 'cygwin'):
return
for root, dirs, files in os.walk(REPO_DIR):
for name in files:
os.chmod(os.path.join(root, name), stat.S_IWRITE)
for name in dirs:
os.chmod(os.path.join(root, name), stat.S_IWRITE)
return
def fail(self):
"""Called if an error occurs.
Take the traceback, log it and notify the MainWindow.
"""
ty, value, tback = sys.exc_info()
msg = ''.join(traceback.format_exception(ty, value, tback))
self.debug(msg)
self.updateFail(msg)
return
def clone(self):
"""If we don't have a copy of the open-ihm repository on disk
clone one now.
"""
try:
self.chmod()
commands.clone(self.ui, self.url, dest=REPO_DIR, insecure=True)
except Exception:
self.fail()
return
def pullAndMerge(self):
"""Run an hg pull and update.
Overwrite all local changes by default.
If anything goes wrong with the pull or update, clone instead.
"""
try:
self.chmod()
commands.pull(self.ui, self.repo, source=self.url)
self.chmod()
commands.update(self.ui, self.repo, clean=True)
except error.RepoError:
if os.path.exists(REPO_DIR):
shutil.rmtree(REPO_DIR)
self.clone()
return
def install(self):
# Use distutils or whatever to install app.
return
def updateFail(self, message):
"""If checking for updates times out (for example, if there
is no current network connection) then fail silently.
"""
self.emit(QtCore.SIGNAL("updateFailure(QString)"), QtCore.QString(message))
return
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self)
self.menubar = QtGui.QMenuBar(self)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
self.menubar.setObjectName("menubar")
self.menuHelp = QtGui.QMenu(self.menubar)
self.menuHelp.setObjectName("menuHelp")
self.menuHelp.setTitle(QtGui.QApplication.translate("MainWindow", "&Help", None, QtGui.QApplication.UnicodeUTF8))
self.menubar.addAction(self.menuHelp.menuAction())
self.actionUpdate = QtGui.QAction(self)
self.actionUpdate.setObjectName("actionUpdate")
self.actionUpdate.setText(QtGui.QApplication.translate("MainWindow", "Update app", None, QtGui.QApplication.UnicodeUTF8))
self.menuHelp.addAction(self.actionUpdate)
QtCore.QObject.connect(self.actionUpdate, QtCore.SIGNAL("triggered()"), self.updateApp)
self.thread = AppUpdator() # For updating software
return
def updateApp(self):
msg = ("Software update starting. \n" +
"Please make sure you have a working Internet connection.\n" +
"This may take some time.")
QtGui.QMessageBox.information(self, "Software update notice", msg)
self.connect(self.thread, QtCore.SIGNAL("updateSuccess()"), self.updateSuccess)
self.connect(self.thread, QtCore.SIGNAL("updateFailure(QString)"), self.updateFailed)
self.thread.start()
return
def updateFailed(self, message):
msg = ("Software update failed.\n" + str(message))
QtGui.QMessageBox.critical(self, "Software update notice", msg)
return
def updateSuccess(self):
msg = ("Software update was successful.\n" +
"Please close open-ihm and restart for changes to take effect.")
QtGui.QMessageBox.information(self, "Software update notice", msg)
return
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment