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]

Reply via email to