dabo Commit
Revision 5232
Date: 2009-05-26 18:15:38 -0700 (Tue, 26 May 2009)
Author: Ed
Trac: http://trac.dabodev.com/changeset/5232
Changed:
U trunk/dabo/dApp.py
U trunk/dabo/settings.py
U trunk/dabo/ui/uiwx/uiApp.py
Log:
This is a complete re-write of the Web Update functionality, in preparation for
a new release.
I've abandoned the practice of updating each project (main framework, Class
Designer, Editor, etc.) separately, and now use a single update for all. This
requires that the user specify where they have placed the ide and demo
directories. Right now there is no way to change them once they are set, but
this will be added to the preference page for Web Update soon.
It also moves the Web Update server functionality off of my home server and
into my cloud server. This will make it more reliable and much faster.
Diff:
Modified: trunk/dabo/dApp.py
===================================================================
--- trunk/dabo/dApp.py 2009-05-21 19:56:47 UTC (rev 5231)
+++ trunk/dabo/dApp.py 2009-05-27 01:15:38 UTC (rev 5232)
@@ -12,6 +12,9 @@
import urllib2
import shutil
import logging
+import simplejson
+from cStringIO import StringIO
+from zipfile import ZipFile
import dabo
import dabo.ui
import dabo.db
@@ -200,7 +203,7 @@
# the current database transaction
self._transactionTokens = {}
# Holds update check times in case of errors.
- self._lastCheckInfo = []
+ self._lastCheckInfo = None
# Location and Name of the project; used for Web Update
self._projectInfo = (None, None)
self._setProjInfo()
@@ -478,19 +481,14 @@
self._appInfo[item] = value
- def _currentUpdateVersion(self, proj):
- if proj == "Dabo":
- localVers = dabo.version["file_revision"]
- try:
- localVers = localVers.split(":")[1]
- except IndexError:
- # Not a mixed version
- pass
- ret = int("".join([ch for ch in localVers if
ch.isdigit()]))
- else:
- ret = self.PreferenceManager.getValue("current_version")
- if ret is None:
- ret = 0
+ def _currentUpdateVersion(self):
+ localVers = dabo.version["file_revision"]
+ try:
+ localVers = localVers.split(":")[1]
+ except IndexError:
+ # Not a mixed version
+ pass
+ ret = int("".join([ch for ch in localVers if ch.isdigit()]))
return ret
@@ -498,8 +496,10 @@
"""Sets the time that Web Update was last checked to the passed
value. Used
in cases where errors prevent an update from succeeding.
"""
- for setter, val in self._lastCheckInfo:
- setter("last_check", val)
+ if self._lastCheckInfo is None:
+ return
+ setter, val = self._lastCheckInfo
+ setter("last_check", val)
def checkForUpdates(self, evt=None):
@@ -517,142 +517,130 @@
"""This is the actual code that checks if a) we are using Web
Update; b) if we are
due for a check; and then c) returns the status of the
available updates, if any.
"""
- frameloc = dabo.frameworkPath
- pth, projectName = self._projectInfo
if not force:
# Check for cases where we absolutely will not Web
Update.
update = dabo.settings.checkForWebUpdates
if update:
# If they are running Subversion, don't update.
- if pth is None:
- pth = frameloc
- if os.path.isdir(os.path.join(pth, ".svn")):
+ if
os.path.isdir(os.path.join(dabo.frameworkPath, ".svn")):
update = False
# Frozen App:
if hasattr(sys, "frozen") and
inspect.stack()[-1][1] != "daborun.py":
update = False
-
if not update:
self._setWebUpdate(False)
- return (False, [])
+ return (False, {})
# First check the framework. If it has updates available,
return that info. If not,
# see if this is an updatable project. If so, check if it has
updates available, and
# return that info.
- prefs = [("Dabo", self._frameworkPrefs)]
- if projectName is not None:
- prefs.insert(0, (projectName, self.PreferenceManager))
- retFirstTime = False
- retUpdateNames = []
- self._lastCheckInfo = []
- for nm, prf in prefs:
- val = prf.getValue
- abbrev = self.projectAbbrevs.get(nm.lower())
- retFirstTime = (nm == "Dabo") and not
prf.hasKey("web_update")
- retAvailable = False
- lastcheck = val("last_check")
- # Store the pref setter and the time so that we can
reset the value
- # in case the update fails later.
- self._lastCheckInfo.append((prf.setValue, lastcheck))
- if not retFirstTime:
- runCheck = force
- now = datetime.datetime.now()
- if not force:
- webUpdate = val("web_update")
- if webUpdate:
- checkInterval =
val("update_interval")
- if checkInterval is None:
- # Default to one day
- checkInterval = 24 * 60
- mins =
datetime.timedelta(minutes=checkInterval)
- if lastcheck is None:
- lastcheck =
datetime.datetime(1900, 1, 1)
- runCheck = (now > (lastcheck +
mins))
- if runCheck:
- # See if there is a later version
- url =
"http://dabodev.com/frameworkVersions/latest?project=%s" % abbrev
- try:
- vers =
int(urllib2.urlopen(url).read())
- except urllib2.URLError:
- # Could not connect
- dabo.errorLog.write(_("Could
not connect to the Dabo servers"))
- return (None, None)
- except ValueError:
- vers = -1
- except StandardError, e:
- dabo.errorLog.write(_("Failed
to open URL '%(url)s'. Error: %(e)s") % locals())
- return (None, None)
- localVers =
self._currentUpdateVersion(nm)
- retAvailable = (localVers < vers)
- prf.setValue("last_check", now)
- if retFirstTime or retAvailable:
- retUpdateNames.append(nm)
- return (retFirstTime, retUpdateNames)
+ prf = self._frameworkPrefs
+ resp = {}
+ val = prf.getValue
+ firstTime = prf.hasKey("web_update")
+ lastcheck = val("last_check")
+ # Hold this in case the update fails.
+ self._lastCheckInfo = (val, lastcheck)
-
- def _updateFramework(self, projNames=None):
- """Get any changed files from the dabodev.com server, and
replace
- the local copies with them. Return the new revision number"""
- fileurl =
"http://dabodev.com/frameworkVersions/changedFiles/%s/%s"
- if projNames is None:
- dabo.errorLog.write(_("No project specified for
_updateFramework()"))
- return
- for pn in projNames:
- currvers = self._currentUpdateVersion(pn)
- if pn == "Dabo":
- localBasePath = dabo.frameworkPath
- else:
- localBasePath = self._projectInfo[0]
- abbrev = self.projectAbbrevs[pn.lower()]
- webpath = self.webUpdateDirs[pn.lower()]
+ runCheck = force
+ now = datetime.datetime.now()
+ if not force:
+ webUpdate = val("web_update")
+ if webUpdate:
+ checkInterval = val("update_interval")
+ if checkInterval is None:
+ # Default to one day
+ checkInterval = 24 * 60
+ mins = datetime.timedelta(minutes=checkInterval)
+ if lastcheck is None:
+ lastcheck = datetime.datetime(1900, 1,
1)
+ runCheck = (now > (lastcheck + mins))
+
+ if runCheck:
+ currVers = self._currentUpdateVersion()
+ # See if there is a later version
+ url = "%s/check/%s" % (dabo.webupdate_urlbase, currVers)
try:
- resp = urllib2.urlopen(fileurl % (abbrev,
currvers))
+ resp =
simplejson.loads(urllib2.urlopen(url).read())
+ except urllib2.URLError, e:
+ # Could not connect
+ dabo.errorLog.write(_("Could not connect to the
Dabo servers: %s") % e)
+ return e
+ except ValueError:
+ vers = -1
except StandardError, e:
- # No internet access, or Dabo site is down.
- dabo.errorLog.write(_("Cannot access the Dabo
site. Error: %s") % e)
- self._resetWebUpdateCheck()
- return None
- respFiles = resp.read()
- if not respFiles:
- # No updates available
- dabo.infoLog.write(_("No changed files
available for %s.") % pn)
- continue
- flist = eval(respFiles)
- # First element is the web directory
- webdir = flist.pop(0)
- url = "http://dabodev.com/versions/%s/%s"
- for mtype, fpth in flist:
- localFile = os.path.join(localBasePath, fpth)
- localPath = os.path.dirname(localFile)
- if mtype == "D" and os.path.exists(localFile):
- if os.path.isdir(localFile):
- shutil.rmtree(localFile)
- else:
- os.remove(localFile)
- else:
- if not os.path.isdir(localPath):
- os.makedirs(localPath)
- if (mtype == "M") and
os.path.isdir(localFile):
- # The folder was modified, such
as by a permission change.
- # We should ignore this.
- continue
- # Permission exceptions should be
caught by the calling method.
- resp = urllib2.urlopen(url % (webdir,
fpth))
- file(localFile, "w").write(resp.read())
- url = "http://dabodev.com/frameworkVersions/latest"
- vers = None
+ dabo.errorLog.write(_("Failed to open URL
'%(url)s'. Error: %(e)s") % locals())
+ return e
+ prf.setValue("last_check", now)
+ return (firstTime, resp)
+
+
+ def _updateFramework(self):
+ """Get any changed files from the dabodev.com server, and
replace
+ the local copies with them."""
+ currVers = self._currentUpdateVersion()
+ fileurl = "%s/files/%s" % (dabo.webupdate_urlbase, currVers)
try:
- vers = int(urllib2.urlopen(url).read())
- except ValueError:
- vers = self._currentUpdateVersion()
+ resp = urllib2.urlopen(fileurl)
except StandardError, e:
+ # No internet access, or Dabo site is down.
dabo.errorLog.write(_("Cannot access the Dabo site.
Error: %s") % e)
- vers = self._currentUpdateVersion()
- if vers:
- self.PreferenceManager.setValue("current_version", vers)
- return vers
+ self._resetWebUpdateCheck()
+ return None
+ f = StringIO(resp.read())
+ zip = ZipFile(f)
+ zipfiles = zip.namelist()
+ if "DELETEDFILES" in zipfiles:
+ delfiles = zip.read("DELETEDFILES").splitlines()
+ else:
+ delfiles = []
+ if not zipfiles:
+ # No updates available
+ dabo.infoLog.write(_("No changed files available."))
+ return
+ projects = ("dabo", "demo", "ide")
+ prf = self._frameworkPrefs
+ loc_demo = prf.getValue("demo_directory")
+ loc_ide = prf.getValue("ide_directory")
+ updates = {}
+ for project in projects:
+ updates[project] = [f for f in zipfiles
+ if f.startswith(project)]
+ need_demoPath = (not loc_demo) and updates["demo"]
+ need_idePath = (not loc_ide) and updates["ide"]
+ if need_idePath or need_demoPath:
+ missing = []
+ if need_demoPath and not loc_demo:
+ missing.append(_("Demo path is missing"))
+ if need_idePath and not loc_ide:
+ missing.append(_("IDE path is missing"))
+ if missing:
+ return "\n".join(missing)
+ locations = {"dabo": dabo.frameworkPath,
+ "demo": loc_demo,
+ "ide": loc_ide}
+ for project in projects:
+ chgs = updates[project]
+ if not chgs:
+ continue
+ os.chdir(locations[project])
+ prefix = "%s/" % project
+ for pth in chgs:
+ target = pth.replace(prefix, "")
+ dirname = os.path.split(target)[0]
+ if dirname and not os.path.exists(dirname):
+ os.makedirs(dirname)
+ file(target, "wb").write(zip.read(pth))
+ for delfile in delfiles:
+ if delfile.startswith(prefix):
+ target = delfile.replace(prefix, "")
+ shutil.rmtree(target,
ignore_errors=True)
+ return True
+
+
+
def _setWebUpdate(self, auto, interval=None):
"""Sets the web update settings for the entire framework. If
set to True, the
interval is expected to be in minutes between checks.
@@ -1260,10 +1248,11 @@
return None
def _setActiveForm(self, frm):
- if hasattr(self, "uiApp") and self.uiApp is not None:
+ try:
self.uiApp._setActiveForm(frm)
- else:
- dabo.errorLog.write(_("Can't set ActiveForm: no
uiApp."))
+ except AttributeError:
+ # self.uiApp hasn't been created yet.
+ pass
def _getBasePrefKey(self):
@@ -1337,7 +1326,11 @@
def _getDrawSizerOutlines(self):
- return self.uiApp.DrawSizerOutlines
+ try:
+ return self.uiApp.DrawSizerOutlines
+ except AttributeError:
+ # self.uiApp hasn't been created yet.
+ return False
def _setDrawSizerOutlines(self, val):
self.uiApp.DrawSizerOutlines = val
Modified: trunk/dabo/settings.py
===================================================================
--- trunk/dabo/settings.py 2009-05-21 19:56:47 UTC (rev 5231)
+++ trunk/dabo/settings.py 2009-05-27 01:15:38 UTC (rev 5232)
@@ -172,6 +172,9 @@
# For file-based data backends such as SQLite, do we allow creating a
connection to
# a non-existent file, which SQLite will then create?
createDbFiles = False
+
+# URL of the Web Update server
+webupdate_urlbase = "http://daboserver.com/webupdate"
### Settings - end
Modified: trunk/dabo/ui/uiwx/uiApp.py
===================================================================
--- trunk/dabo/ui/uiwx/uiApp.py 2009-05-21 19:56:47 UTC (rev 5231)
+++ trunk/dabo/ui/uiwx/uiApp.py 2009-05-27 01:15:38 UTC (rev 5232)
@@ -206,26 +206,83 @@
answer = False
msg = ""
vers = None
- isFirst, projNames = self.dApp._checkForUpdates(force=force)
- updAvail = bool(projNames)
- if isFirst:
- msg = _("This appears to be the first time you are
running Dabo. If you are "
- "connected to the internet, Dabo will
check for updates and install them "
- "if you wish. Click 'Yes' to get the
latest version of Dabo now, or 'No' if "
- "you do not have internet access.")
- # Default to checking once a day.
- self.dApp._setWebUpdate(True, 24*60)
- elif updAvail:
- if len(projNames) > 1:
- nms = " and ".join(projNames)
- else:
- nms = projNames[0]
- msg = _("Updates are available for %s. Do you want to
update now?") % nms
+ updAvail = False
+ checkResult = self.dApp._checkForUpdates(force=force)
+ if isinstance(checkResult, Exception):
+ dabo.ui.stop(_("There was an error encountered when
checking Web Update: %s") % checkResult,
+ _("Web Update Problem"))
+ else:
+ # The response will be a boolean for 'first time',
along with the dict of updates.
+ isFirst, updates = checkResult
+ fileUpdates = updates.get("files")
+ updAvail = bool(fileUpdates)
+ if updAvail:
+ notes = ["%s: %s" % tuple(nt) for nt in
updates.get("notes", "")]
+ noteText = "\n\n".join(notes)
+ # The format of each entry is the output from
svn, and has the format:
+ # 'M
dabo/ui/uiwx/dFormMixin.py'
+ # We need to break that up into: (changeType,
project, file)
+ step1 = [ch.split() for ch in fileUpdates]
+ # This is now in the format of [['M',
'dabo/ui/uiwx/dFormMixin.py'], ...]
+ # Filter out non-standard projects. Do this
first, since some base trunk
+ # files can be in the list, and will throw an
IndexError.
+ step2 = [(ch[0], ch[1]) for ch in step1
+ if ch[1].split("/", 1)[0] in
("dabo", "demo", "ide")]
+ step2.sort(lambda x,y: cmp(x[1], y[1]))
+ # Now split off the project
+ step3 = [{"mod":ch[0],
"project":ch[1].split("/", 1)[0], "file":ch[1].split("/", 1)[1]} for ch in
step2]
+ changedFiles = step3
+ updAvail = bool(changedFiles)
+
+ if updAvail:
+ msg = _("Updates are available. Do you want to install
them now?")
+ if isFirst:
+ msg = _(
+"""This appears to be the first time you are running Dabo. When starting
+up, Dabo will check for updates and install them if you wish. Click 'Yes'
+to get the latest version of Dabo now, or 'No' if you do not wish to run
+these automatic updates.""").replace("\n", " ")
+ # Default to checking once a day.
+ self.dApp._setWebUpdate(True, 24*60)
+
if msg:
- answer = dabo.ui.areYouSure(msg, title=_("Web Update
Available"), cancelButton=False)
+ class WebUpdateConfirmDialog(dabo.ui.dYesNoDialog):
+ def initProperties(self):
+ self.Caption = "Updates Available"
+ self.AutoSize = False
+ self.Height = 600
+ self.Width = 500
+ def addControls(self):
+ headline = dabo.ui.dLabel(self,
Caption=msg, FontSize=10,
+ WordWrap=True,
ForeColor="blue")
+ self.Sizer.append1x(headline,
halign="center")
+ self.Sizer.appendSpacer(12)
+ edtNotes = dabo.ui.dEditBox(self,
Value=noteText)
+ self.Sizer.append(edtNotes, 1, "x")
+ self.Sizer.appendSpacer(12)
+ grd = dabo.ui.dGrid(self, ColumnCount=3)
+ col0, col1, col2 = grd.Columns
+ col0.DataField = "mod"
+ col1.DataField = "project"
+ col2.DataField = "file"
+ col0.Caption = "Change"
+ col1.Caption = "Project"
+ col2.Caption = "File"
+ col0.Width = 60
+ col1.Width = 60
+ col2.Width = 300
+ grd.DataSet = changedFiles
+ self.Sizer.append(grd, 3, "x")
+ self.Sizer.appendSpacer(20)
+
+ dlg = WebUpdateConfirmDialog()
+ dlg.show()
+ answer = dlg.Accepted
+ dlg.release()
if answer:
+ self._setUpdatePathLocations()
try:
- vers =
self.dApp._updateFramework(projNames)
+ success = self.dApp._updateFramework()
except IOError, e:
dabo.errorLog.write(_("Cannot update
files; Error: %s") % e)
dabo.ui.info(_("You do not have
permission to update the necessary files. "
@@ -233,22 +290,73 @@
self.dApp._resetWebUpdateCheck()
answer = False
- if vers is None:
+ if success is None:
# Update was not successful
dabo.ui.info(_("There was a problem
getting a response from the Dabo site. "
"Please check your
internet connection and try again later."), title=_("Update Failed"))
answer = False
self.dApp._resetWebUpdateCheck()
- elif vers == 0:
+ elif isinstance(success, basestring):
+ # Error message was returned
+ dabo.ui.stop(success, title=_("Update
Failure"))
+ elif success is False:
# There were no changed files available.
dabo.ui.info(_("There were no changed
files available - your system is up-to-date!"),
title=_("No Update
Needed"))
answer = False
else:
- dabo.ui.info(_("Dabo has been updated
to revision %s. The app "
- "will now exit. Please
re-run the application.") % vers, title=_("Success!"))
+ dabo.ui.info(_("Dabo has been updated
to the current revision. The app "
+ "will now exit. Please
re-run the application."), title=_("Success!"))
return not answer
+
+
+ def _setUpdatePathLocations(self):
+ projects = ("dabo", "demo", "ide")
+ prf = self.dApp._frameworkPrefs
+ loc_demo = prf.getValue("demo_directory")
+ loc_ide = prf.getValue("ide_directory")
+ if loc_demo and loc_ide:
+ return
+
+ class PathDialog(dabo.ui.dOkCancelDialog):
+ def initProperties(self):
+ self.Caption = _("Dabo Project Locations")
+ def addControls(self):
+ gsz = dabo.ui.dGridSizer(MaxCols=3)
+ lbl = dabo.ui.dLabel(self, Caption=_("IDE
Directory:"))
+ txt = dabo.ui.dTextBox(self, Enabled=False,
Value=loc_ide, RegID="txtIDE", Width=200)
+ btn = dabo.ui.dButton(self, Caption="...",
OnHit=self.onGetIdeDir)
+ gsz.appendItems((lbl, txt, btn), border=5)
+ gsz.appendSpacer(10, colSpan=3)
+ lbl = dabo.ui.dLabel(self, Caption=_("Demo
Directory:"))
+ txt = dabo.ui.dTextBox(self, Enabled=False,
Value=loc_demo, RegID="txtDemo", Width=200)
+ btn = dabo.ui.dButton(self, Caption="...",
OnHit=self.onGetDemoDir)
+ gsz.appendItems((lbl, txt, btn), border=5)
+ gsz.setColExpand(True, 1)
+ self.Sizer.append(gsz, halign="center",
border=10)
+ self.Sizer.appendSpacer(25)
+ def onGetIdeDir(self, evt):
+ default = loc_ide
+ if default is None:
+ default = dabo.frameworkPath
+ f = dabo.ui.getDirectory(_("Select the location
of the IDE folder"), defaultPath=default)
+ if f:
+ self.txtIDE.Value = f
+ def onGetDemoDir(self, evt):
+ default = loc_demo
+ if default is None:
+ default = dabo.frameworkPath
+ f = dabo.ui.getDirectory(_("Select the location
of the Demo folder"), defaultPath=default)
+ if f:
+ self.txtDemo.Value = f
+ dlg = PathDialog()
+ dlg.show()
+ if dlg.Accepted:
+ prf.ide_directory = dlg.txtIDE.Value
+ prf.demo_directory = dlg.txtDemo.Value
+ dlg.release()
+
def _onKeyPress(self, evt):
## Zoom In / Out / Normal:
_______________________________________________
Post Messages to: [email protected]
Subscription Maintenance: http://leafe.com/mailman/listinfo/dabo-dev
Searchable Archives: http://leafe.com/archives/search/dabo-dev
This message:
http://leafe.com/archives/byMID/[email protected]