| 1 |
# -*- coding: utf-8 -*- |
|---|
| 2 |
import sys |
|---|
| 3 |
import os |
|---|
| 4 |
import locale |
|---|
| 5 |
import warnings |
|---|
| 6 |
import glob |
|---|
| 7 |
import tempfile |
|---|
| 8 |
import imp |
|---|
| 9 |
import ConfigParser |
|---|
| 10 |
import inspect |
|---|
| 11 |
import datetime |
|---|
| 12 |
import urllib2 |
|---|
| 13 |
import shutil |
|---|
| 14 |
import logging |
|---|
| 15 |
import dabo |
|---|
| 16 |
import dabo.ui |
|---|
| 17 |
import dabo.db |
|---|
| 18 |
import dabo.dLocalize as dLocalize |
|---|
| 19 |
import dabo.dException as dException |
|---|
| 20 |
from dabo.dLocalize import _ |
|---|
| 21 |
from dabo.lib.connParser import importConnections |
|---|
| 22 |
from dabo import dSecurityManager |
|---|
| 23 |
from dabo.lib.SimpleCrypt import SimpleCrypt |
|---|
| 24 |
from dabo.dObject import dObject |
|---|
| 25 |
from dabo import dUserSettingProvider |
|---|
| 26 |
from dabo.lib.RemoteConnector import RemoteConnector |
|---|
| 27 |
|
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 |
class Collection(list): |
|---|
| 31 |
""" Collection : Base class for the various collection |
|---|
| 32 |
classes used in the app object. |
|---|
| 33 |
""" |
|---|
| 34 |
def __init__(self): |
|---|
| 35 |
list.__init__(self) |
|---|
| 36 |
|
|---|
| 37 |
|
|---|
| 38 |
def add(self, objRef): |
|---|
| 39 |
"""Add the object reference to the collection.""" |
|---|
| 40 |
self.append(objRef) |
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 43 |
def remove(self, objRef): |
|---|
| 44 |
"""Delete the object reference from the collection.""" |
|---|
| 45 |
try: |
|---|
| 46 |
index = self.index(objRef) |
|---|
| 47 |
except ValueError: |
|---|
| 48 |
index = None |
|---|
| 49 |
if index is not None: |
|---|
| 50 |
del self[index] |
|---|
| 51 |
|
|---|
| 52 |
|
|---|
| 53 |
|
|---|
| 54 |
class TempFileHolder(object): |
|---|
| 55 |
"""Utility class to get temporary file names and to make sure they are |
|---|
| 56 |
deleted when the Python session ends. |
|---|
| 57 |
""" |
|---|
| 58 |
def __init__(self): |
|---|
| 59 |
self._tempFiles = [] |
|---|
| 60 |
|
|---|
| 61 |
|
|---|
| 62 |
def __del__(self): |
|---|
| 63 |
self._eraseTempFiles() |
|---|
| 64 |
|
|---|
| 65 |
|
|---|
| 66 |
def _eraseTempFiles(self): |
|---|
| 67 |
# Try to erase all temp files created during life. |
|---|
| 68 |
# Need to re-import the os module here for some reason. |
|---|
| 69 |
try: |
|---|
| 70 |
import os |
|---|
| 71 |
for f in self._tempFiles: |
|---|
| 72 |
if not os.path.exists(f): |
|---|
| 73 |
continue |
|---|
| 74 |
try: |
|---|
| 75 |
os.remove(f) |
|---|
| 76 |
except OSError, e: |
|---|
| 77 |
if not f.endswith(".pyc"): |
|---|
| 78 |
# Don't worry about the .pyc files, since they may not be there |
|---|
| 79 |
print "Could not delete %s: %s" % (f, e) |
|---|
| 80 |
except StandardError, e: |
|---|
| 81 |
# In these rare cases, Python has already 'gone away', so just bail |
|---|
| 82 |
pass |
|---|
| 83 |
|
|---|
| 84 |
|
|---|
| 85 |
def release(self): |
|---|
| 86 |
self._eraseTempFiles() |
|---|
| 87 |
|
|---|
| 88 |
|
|---|
| 89 |
def append(self, f): |
|---|
| 90 |
self._tempFiles.append(f) |
|---|
| 91 |
|
|---|
| 92 |
|
|---|
| 93 |
def getTempFile(self, ext=None, badChars=None, directory=None): |
|---|
| 94 |
if ext is None: |
|---|
| 95 |
ext = "py" |
|---|
| 96 |
if badChars is None: |
|---|
| 97 |
badChars = "-:" |
|---|
| 98 |
fname = "" |
|---|
| 99 |
suffix = ".%s" % ext |
|---|
| 100 |
while not fname: |
|---|
| 101 |
if directory is None: |
|---|
| 102 |
fd, tmpname = tempfile.mkstemp(suffix=suffix) |
|---|
| 103 |
else: |
|---|
| 104 |
fd, tmpname = tempfile.mkstemp(suffix=suffix, dir=directory) |
|---|
| 105 |
os.close(fd) |
|---|
| 106 |
bad = [ch for ch in badChars if ch in os.path.split(tmpname)[1]] |
|---|
| 107 |
if not bad: |
|---|
| 108 |
fname = tmpname |
|---|
| 109 |
self.append(fname) |
|---|
| 110 |
if fname.endswith(".py"): |
|---|
| 111 |
# Track the .pyc file, too. |
|---|
| 112 |
self.append(fname + "c") |
|---|
| 113 |
return fname |
|---|
| 114 |
|
|---|
| 115 |
|
|---|
| 116 |
|
|---|
| 117 |
class dApp(dObject): |
|---|
| 118 |
"""The containing object for the entire application. |
|---|
| 119 |
|
|---|
| 120 |
All Dabo objects have an Application property which refers to the dApp |
|---|
| 121 |
instance. Instantiate your dApp object from your main script, like so: |
|---|
| 122 |
|
|---|
| 123 |
>>> import dabo |
|---|
| 124 |
>>> app = dabo.dApp |
|---|
| 125 |
>>> app.start() |
|---|
| 126 |
|
|---|
| 127 |
Normally, dApp gets instantiated from the client app's main Python script, |
|---|
| 128 |
and lives through the life of the application. |
|---|
| 129 |
|
|---|
| 130 |
-- set up an empty data connections object which holds |
|---|
| 131 |
-- connectInfo objects connected to pretty names. If there |
|---|
| 132 |
-- is a file named 'default.cnxml' present, it will import the |
|---|
| 133 |
-- connection definitions contained in that. If no file of that |
|---|
| 134 |
-- name exists, it will import any .cnxml file it finds. If there |
|---|
| 135 |
-- are no such files, it will then revert to the old behavior |
|---|
| 136 |
-- of importing a file in the current directory called |
|---|
| 137 |
-- 'dbConnectionDefs.py', which contains connection |
|---|
| 138 |
-- definitions in python code format instead of XML. |
|---|
| 139 |
|
|---|
| 140 |
-- Set up a DB Connection manager, that is basically a dictionary |
|---|
| 141 |
-- of dConnection objects. This allows connections to be shared |
|---|
| 142 |
-- application-wide. |
|---|
| 143 |
|
|---|
| 144 |
-- decide which ui to use (wx) and gets that ball rolling |
|---|
| 145 |
|
|---|
| 146 |
-- look for a MainForm in an expected place, otherwise use default dabo |
|---|
| 147 |
-- dMainForm, and instantiate that. |
|---|
| 148 |
|
|---|
| 149 |
-- maintain a forms collection and provide interfaces for |
|---|
| 150 |
-- opening dForms, closing them, and iterating through them. |
|---|
| 151 |
|
|---|
| 152 |
-- start the main app event loop. |
|---|
| 153 |
|
|---|
| 154 |
-- clean up and exit gracefully |
|---|
| 155 |
|
|---|
| 156 |
""" |
|---|
| 157 |
_call_beforeInit, _call_afterInit, _call_initProperties = False, False, True |
|---|
| 158 |
# Behaviors which are normal in the framework may need to |
|---|
| 159 |
# be modified when run as the Designer. This flag will |
|---|
| 160 |
# distinguish between the two states. |
|---|
| 161 |
isDesigner = False |
|---|
| 162 |
|
|---|
| 163 |
|
|---|
| 164 |
def __init__(self, selfStart=False, properties=None, *args, **kwargs): |
|---|
| 165 |
if dabo.settings.loadUserLocale: |
|---|
| 166 |
locale.setlocale(locale.LC_ALL, '') |
|---|
| 167 |
|
|---|
| 168 |
self._uiAlreadySet = False |
|---|
| 169 |
dabo.dAppRef = self |
|---|
| 170 |
self._beforeInit() |
|---|
| 171 |
|
|---|
| 172 |
# If we are displaying a splash screen, these attributes control |
|---|
| 173 |
# its appearance. Extract them before the super call. |
|---|
| 174 |
self.showSplashScreen = self._extractKey(kwargs, "showSplashScreen", False) |
|---|
| 175 |
basepath = dabo.frameworkPath |
|---|
| 176 |
img = os.path.join(basepath, "icons", "daboSplashName.png") |
|---|
| 177 |
self.splashImage = self._extractKey(kwargs, "splashImage", img) |
|---|
| 178 |
self.splashMaskColor = self._extractKey(kwargs, "splashMaskColor", None) |
|---|
| 179 |
self.splashTimeout = self._extractKey(kwargs, "splashTimeout", 5000) |
|---|
| 180 |
|
|---|
| 181 |
super(dApp, self).__init__(properties, *args, **kwargs) |
|---|
| 182 |
# egl: added the option of keeping the main form hidden |
|---|
| 183 |
# initially. The default behavior is for it to be shown, as usual. |
|---|
| 184 |
self.showMainFormOnStart = True |
|---|
| 185 |
self._wasSetup = False |
|---|
| 186 |
# Track names of menus whose MRUs need to be persisted. Set |
|---|
| 187 |
# the key for each entry to the menu caption, and the value to |
|---|
| 188 |
# the bound function. |
|---|
| 189 |
self._persistentMRUs = {} |
|---|
| 190 |
# Create the temp file handlers. |
|---|
| 191 |
self._tempFileHolder = TempFileHolder() |
|---|
| 192 |
self.getTempFile = self._tempFileHolder.getTempFile |
|---|
| 193 |
# Create the framework-level preference manager |
|---|
| 194 |
self._frameworkPrefs = dabo.dPref(key="dabo_framework") |
|---|
| 195 |
# Hold a reference to the bizobj and connection, if any, controlling |
|---|
| 196 |
# the current database transaction |
|---|
| 197 |
self._transactionTokens = {} |
|---|
| 198 |
# Holds update check times in case of errors. |
|---|
| 199 |
self._lastCheckInfo = [] |
|---|
| 200 |
# Location and Name of the project; used for Web Update |
|---|
| 201 |
self._projectInfo = (None, None) |
|---|
| 202 |
self._setProjInfo() |
|---|
| 203 |
# Other Web Update values |
|---|
| 204 |
self.projectAbbrevs = {"dabo": "frm", |
|---|
| 205 |
"class designer": "cds", |
|---|
| 206 |
"cxn editor": "cxe", |
|---|
| 207 |
"editor": "edt", |
|---|
| 208 |
"menu designer": "mds", |
|---|
| 209 |
"preference editor": "prf", |
|---|
| 210 |
"report designer": "rds", |
|---|
| 211 |
"wizards": "wiz", |
|---|
| 212 |
"dabodemo": "dem"} |
|---|
| 213 |
self.webUpdateDirs = {"dabo": "dabo", |
|---|
| 214 |
"class designer": "ide", |
|---|
| 215 |
"cxn editor": "ide", |
|---|
| 216 |
"editor": "ide", |
|---|
| 217 |
"menu designer": "ide", |
|---|
| 218 |
"preference editor": "ide", |
|---|
| 219 |
"report designer": "ide", |
|---|
| 220 |
"wizards": "ide", |
|---|
| 221 |
"dabodemo": "demo"} |
|---|
| 222 |
|
|---|
| 223 |
# List of form classes to open on App Startup |
|---|
| 224 |
self.formsToOpen = [] |
|---|
| 225 |
# Form to open if no forms were passed as a parameter |
|---|
| 226 |
self.default_form = None |
|---|
| 227 |
# Dict of "Last-Modified" values for dynamic web resources |
|---|
| 228 |
self._sourceLastModified = {} |
|---|
| 229 |
|
|---|
| 230 |
# For simple UI apps, this allows the app object to be created |
|---|
| 231 |
# and started in one step. It also suppresses the display of |
|---|
| 232 |
# the main form. |
|---|
| 233 |
if selfStart: |
|---|
| 234 |
self.showMainFormOnStart = False |
|---|
| 235 |
self.setup() |
|---|
| 236 |
|
|---|
| 237 |
self._initDB() |
|---|
| 238 |
|
|---|
| 239 |
# If running as a web app, sync the files |
|---|
| 240 |
rp = self._RemoteProxy |
|---|
| 241 |
if rp: |
|---|
| 242 |
try: |
|---|
| 243 |
rp.syncFiles() |
|---|
| 244 |
except urllib2.URLError, e: |
|---|
| 245 |
code, msg = e.reason |
|---|
| 246 |
if code == 61: |
|---|
| 247 |
# Connection refused; server's down |
|---|
| 248 |
print _(""" |
|---|
| 249 |
|
|---|
| 250 |
|
|---|
| 251 |
The connection was refused by the server. Most likely this means that |
|---|
| 252 |
the server is not running. Please have that problem corrected, and |
|---|
| 253 |
try again when it is running. |
|---|
| 254 |
|
|---|
| 255 |
""") |
|---|
| 256 |
sys.exit(61) |
|---|
| 257 |
|
|---|
| 258 |
self._afterInit() |
|---|
| 259 |
self.autoBindEvents() |
|---|
| 260 |
|
|---|
| 261 |
|
|---|
| 262 |
def __del__(self): |
|---|
| 263 |
"""Make sure that temp files are removed""" |
|---|
| 264 |
self._tempFileHolder.release() |
|---|
| 265 |
|
|---|
| 266 |
|
|---|
| 267 |
def setup(self, initUI=True): |
|---|
| 268 |
"""Set up the application object.""" |
|---|
| 269 |
# dabo is going to want to import various things from the Home Directory |
|---|
| 270 |
if self.HomeDirectory not in sys.path: |
|---|
| 271 |
sys.path.append(self.HomeDirectory) |
|---|
| 272 |
|
|---|
| 273 |
def initAppInfo(item, default): |
|---|
| 274 |
if not self.getAppInfo(item): |
|---|
| 275 |
self.setAppInfo(item, default) |
|---|
| 276 |
|
|---|
| 277 |
initAppInfo("appName", "Dabo Application") |
|---|
| 278 |
initAppInfo("appShortName", self.getAppInfo("appName").replace(" ", "")) |
|---|
| 279 |
initAppInfo("appVersion", "") |
|---|
| 280 |
initAppInfo("vendorName", "") |
|---|
| 281 |
|
|---|
| 282 |
# If there's a locale directory for the app and it looks valid, install it: |
|---|
| 283 |
localeDir = os.path.join(self.HomeDirectory, "locale") |
|---|
| 284 |
localeDomain = self.getAppInfo("appShortName").replace(" ", "_").lower() |
|---|
| 285 |
if os.path.isdir(localeDir) and dLocalize.isValidDomain(localeDomain, localeDir): |
|---|
| 286 |
lang = getattr(self, "_language", None) |
|---|
| 287 |
charset = getattr(self, "_charset", None) |
|---|
| 288 |
dLocalize.install(localeDomain, localeDir) |
|---|
| 289 |
dLocalize.setLanguage(lang, charset) |
|---|
| 290 |
|
|---|
| 291 |
self._initModuleNames() |
|---|
| 292 |
self._initDB() |
|---|
| 293 |
|
|---|
| 294 |
if initUI: |
|---|
| 295 |
self._initUI() |
|---|
| 296 |
if self.UI is not None: |
|---|
| 297 |
if self.showSplashScreen: |
|---|
| 298 |
#self.uiApp = dabo.ui.uiApp(self, callback=self.initUIApp) |
|---|
| 299 |
self.uiApp = dabo.ui.getUiApp(self, callback=self.initUIApp) |
|---|
| 300 |
else: |
|---|
| 301 |
#self.uiApp = dabo.ui.uiApp(self, callback=None) |
|---|
| 302 |
self.uiApp = dabo.ui.getUiApp(self, callback=None) |
|---|
| 303 |
self.initUIApp() |
|---|
| 304 |
else: |
|---|
| 305 |
self.uiApp = None |
|---|
| 306 |
# Flip the flag |
|---|
| 307 |
self._wasSetup = True |
|---|
| 308 |
# Call the afterSetup hook |
|---|
| 309 |
self.afterSetup() |
|---|
| 310 |
|
|---|
| 311 |
|
|---|
| 312 |
def afterSetup(self): |
|---|
| 313 |
"""Hook method that is called after the app's setup code has run, and the |
|---|
| 314 |
database, UI and module references have all been established. |
|---|
| 315 |
""" |
|---|
| 316 |
pass |
|---|
| 317 |
|
|---|
| 318 |
|
|---|
| 319 |
def startupForms(self): |
|---|
| 320 |
"""Open one or more of the defined forms. The default one is specified |
|---|
| 321 |
in .default_form. If form names were passed on the command line, |
|---|
| 322 |
they will be opened instead of the default one as long as they exist. |
|---|
| 323 |
""" |
|---|
| 324 |
form_names = [class_name[3:] for class_name in dir(self.ui) if class_name[:3] == "Frm"] |
|---|
| 325 |
for arg in sys.argv[1:]: |
|---|
| 326 |
arg = arg.lower() |
|---|
| 327 |
for form_name in form_names: |
|---|
| 328 |
if arg == form_name.lower(): |
|---|
| 329 |
self.formsToOpen.append(getattr(self.ui, "Frm%s" % form_name)) |
|---|
| 330 |
if not self.formsToOpen: |
|---|
| 331 |
self.formsToOpen.append(self.default_form) |
|---|
| 332 |
for frm in self.formsToOpen: |
|---|
| 333 |
frm(self.MainForm).show() |
|---|
| 334 |
|
|---|
| 335 |
def initUIApp(self): |
|---|
| 336 |
"""Callback from the initial app setup. Used to allow the |
|---|
| 337 |
splash screen, if any, to be shown quickly. |
|---|
| 338 |
""" |
|---|
| 339 |
self.uiApp.setup() |
|---|
| 340 |
|
|---|
| 341 |
|
|---|
| 342 |
def start(self): |
|---|
| 343 |
"""Start the application event loop.""" |
|---|
| 344 |
if not self._wasSetup: |
|---|
| 345 |
# Convenience; if you don't need to customize setup(), just |
|---|
| 346 |
# call start() |
|---|
| 347 |
self.setup() |
|---|
| 348 |
|
|---|
| 349 |
self._finished = False |
|---|
| 350 |
if (not self.SecurityManager or not self.SecurityManager.RequireAppLogin |
|---|
| 351 |
or self.SecurityManager.login()): |
|---|
| 352 |
|
|---|
| 353 |
userName = self.getUserCaption() |
|---|
| 354 |
if userName: |
|---|
| 355 |
userName = " (%s)" % userName |
|---|
| 356 |
else: |
|---|
| 357 |
userName = "" |
|---|
| 358 |
|
|---|
| 359 |
self._retrieveMRUs() |
|---|
| 360 |
self.uiApp.start(self) |
|---|
| 361 |
if not self._finished: |
|---|
| 362 |
self.finish() |
|---|
| 363 |
|
|---|
| 364 |
|
|---|
| 365 |
def finish(self): |
|---|
| 366 |
"""Called when the application event loop has ended. You may also |
|---|
| 367 |
call this explicitly to exit the application event loop. |
|---|
| 368 |
""" |
|---|
| 369 |
self.uiApp.exit() |
|---|
| 370 |
self._persistMRU() |
|---|
| 371 |
self.uiApp.finish() |
|---|
| 372 |
self.closeConnections() |
|---|
| 373 |
self._tempFileHolder.release() |
|---|
| 374 |
dabo.infoLog.write(_("Application finished.")) |
|---|
| 375 |
self._finished = True |
|---|
| 376 |
self.afterFinish() |
|---|
| 377 |
|
|---|
| 378 |
|
|---|
| 379 |
def afterFinish(self): |
|---|
| 380 |
"""Stub method. When this is called, the app has already terminated, and you have |
|---|
| 381 |
one last chance to execute code by overriding this method. |
|---|
| 382 |
""" |
|---|
| 383 |
pass |
|---|
| 384 |
|
|---|
| 385 |
|
|---|
| 386 |
def _setProjInfo(self): |
|---|
| 387 |
"""Create a 2-tuple containing the project location and project name, if any. |
|---|
| 388 |
The location is always the directory containing the initial startup program; the |
|---|
| 389 |
name is either None (default), or, if this is a Web Update-able app, the |
|---|
| 390 |
descriptive name. |
|---|
| 391 |
""" |
|---|
| 392 |
relpth = inspect.stack()[-1][1] |
|---|
| 393 |
op = os.path |
|---|
| 394 |
pth, fnm = op.split(op.normpath(op.join(os.getcwd(), relpth))) |
|---|
| 395 |
projnames = {"ClassDesigner.py": "Class Designer", |
|---|
| 396 |
"CxnEditor.py": "Cxn Editor", |
|---|
| 397 |
"Editor.py": "Editor", |
|---|
| 398 |
"MenuDesigner.py": "Menu Designer", |
|---|
| 399 |
"PrefEditor.py": "Preference Editor", |
|---|
| 400 |
"ReportDesigner.py": "Report Designer", |
|---|
| 401 |
"DaboDemo.py": "DaboDemo"} |
|---|
| 402 |
nm = projnames.get(fnm, None) |
|---|
| 403 |
if nm is None: |
|---|
| 404 |
if "wizards" in pth: |
|---|
| 405 |
nm ="Wizards" |
|---|
| 406 |
self._projectInfo = (pth, nm) |
|---|
| 407 |
|
|---|
| 408 |
|
|---|
| 409 |
def getLoginInfo(self, message=None): |
|---|
| 410 |
"""Return a tuple of (user, password) to dSecurityManager.login(). The default is to display the standard login dialog, and return the user/password as entered by the user, but subclasses can override to get the information from whereever is appropriate. You can customize the default dialog by adding your own code to the loginDialogHook() method, which will receive a reference to the login dialog. |
|---|
| 411 |
|
|---|
| 412 |
Return a tuple of (user, pass). |
|---|
| 413 |
""" |
|---|
| 414 |
import dabo.ui.dialogs.login as login |
|---|
| 415 |
ld = login.Login(self.MainForm) |
|---|
| 416 |
ld.setMessage(message) |
|---|
| 417 |
# Allow the developer to customize the default login |
|---|
| 418 |
self.loginDialogHook(ld) |
|---|
| 419 |
ld.show() |
|---|
| 420 |
user, password = ld.user, ld.password |
|---|
| 421 |
return user, password |
|---|
| 422 |
|
|---|
| 423 |
|
|---|
| 424 |
def loginDialogHook(self, dlg): |
|---|
| 425 |
"""Hook method; modify the dialog as needed.""" |
|---|
| 426 |
pass |
|---|
| 427 |
|
|---|
| 428 |
|
|---|
| 429 |
def _persistMRU(self): |
|---|
| 430 |
"""Persist any MRU lists to disk.""" |
|---|
| 431 |
base = "MRU.%s" % self.getAppInfo("appName") |
|---|
| 432 |
self.deleteAllUserSettings(base) |
|---|
| 433 |
for cap in self._persistentMRUs.keys(): |
|---|
| 434 |
mruList = self.uiApp.getMRUListForMenu(cap) |
|---|
| 435 |
setName = ".".join((base, cap)) |
|---|
| 436 |
self.setUserSetting(setName, mruList) |
|---|
| 437 |
|
|---|
| 438 |
|
|---|
| 439 |
def _retrieveMRUs(self): |
|---|
| 440 |
"""Retrieve any saved MRU lists.""" |
|---|
| 441 |
base = "MRU.%s" % self.getAppInfo("appName") |
|---|
| 442 |
for cap, fcn in self._persistentMRUs.items(): |
|---|
| 443 |
itms = self.getUserSetting(".".join((base, cap))) |
|---|
| 444 |
if itms: |
|---|
| 445 |
# Should be a list of items. Add 'em in reverse order |
|---|
| 446 |
for itm in itms: |
|---|
| 447 |
self.uiApp.addToMRU(cap, itm, fcn) |
|---|
| 448 |
|
|---|
| 449 |
|
|---|
| 450 |
def getAppInfo(self, item): |
|---|
| 451 |
"""Look up the item, and return the value.""" |
|---|
| 452 |
try: |
|---|
| 453 |
retVal = self._appInfo[item] |
|---|
| 454 |
except KeyError: |
|---|
| 455 |
retVal = None |
|---|
| 456 |
return retVal |
|---|
| 457 |
|
|---|
| 458 |
|
|---|
| 459 |
def setAppInfo(self, item, value): |
|---|
| 460 |
"""Set item to value in the appinfo table.""" |
|---|
| 461 |
self._appInfo[item] = value |
|---|
| 462 |
|
|---|
| 463 |
|
|---|
| 464 |
def _currentUpdateVersion(self, proj): |
|---|
| 465 |
if proj == "Dabo": |
|---|
| 466 |
localVers = dabo.version["file_revision"] |
|---|
| 467 |
try: |
|---|
| 468 |
localVers = localVers.split(":")[1] |
|---|
| 469 |
except IndexError: |
|---|
| 470 |
# Not a mixed version |
|---|
| 471 |
pass |
|---|
| 472 |
ret = int("".join([ch for ch in localVers if ch.isdigit()])) |
|---|
| 473 |
else: |
|---|
| 474 |
ret = self.PreferenceManager.getValue("current_version") |
|---|
| 475 |
if ret is None: |
|---|
| 476 |
ret = 0 |
|---|
| 477 |
return ret |
|---|
| 478 |
|
|---|
| 479 |
|
|---|
| 480 |
def _resetWebUpdateCheck(self): |
|---|
| 481 |
"""Sets the time that Web Update was last checked to the passed value. Used |
|---|
| 482 |
in cases where errors prevent an update from succeeding. |
|---|
| 483 |
""" |
|---|
| 484 |
for setter, val in self._lastCheckInfo: |
|---|
| 485 |
setter("last_check", val) |
|---|
| 486 |
|
|---|
| 487 |
|
|---|
| 488 |
def checkForUpdates(self, evt=None): |
|---|
| 489 |
"""Public interface to the web updates mechanism. Returns a 2-tuple |
|---|
| 490 |
consisting of a boolean and a list of projects available for update. The boolean |
|---|
| 491 |
indicates whether this is the first time that the framework is being run and the list |
|---|
| 492 |
contains the updatable project names; e.g., if both project 'Foo' and the framework |
|---|
| 493 |
have updates, it will return ["Dabo", "Foo"]; if only the framework has updates, it will |
|---|
| 494 |
return ["Dabo"]. If there are no updates available, an empty list will be returned. |
|---|
| 495 |
""" |
|---|
| 496 |
return self.uiApp.checkForUpdates(force=True) |
|---|
| 497 |
|
|---|
| 498 |
|
|---|
| 499 |
def _checkForUpdates(self, force=False): |
|---|
| 500 |
"""This is the actual code that checks if a) we are using Web Update; b) if we are |
|---|
| 501 |
due for a check; and then c) returns the status of the available updates, if any. |
|---|
| 502 |
""" |
|---|
| 503 |
frameloc = dabo.frameworkPath |
|---|
| 504 |
pth, projectName = self._projectInfo |
|---|
| 505 |
if not force: |
|---|
| 506 |
# Check for cases where we absolutely will not Web Update. |
|---|
| 507 |
update = dabo.settings.checkForWebUpdates |
|---|
| 508 |
if update: |
|---|
| 509 |
# If they are running Subversion, don't update. |
|---|
| 510 |
if pth is None: |
|---|
| 511 |
pth = frameloc |
|---|
| 512 |
if os.path.isdir(os.path.join(pth, ".svn")): |
|---|
| 513 |
update = False |
|---|
| 514 |
# Frozen App: |
|---|
| 515 |
if hasattr(sys, "frozen") and inspect.stack()[-1][1] != "daborun.py": |
|---|
| 516 |
update = False |
|---|
| 517 |
|
|---|
| 518 |
if not update: |
|---|
| 519 |
self._setWebUpdate(False) |
|---|
| 520 |
return (False, []) |
|---|
| 521 |
|
|---|
| 522 |
# First check the framework. If it has updates available, return that info. If not, |
|---|
| 523 |
# see if this is an updatable project. If so, check if it has updates available, and |
|---|
| 524 |
# return that info. |
|---|
| 525 |
prefs = [("Dabo", self._frameworkPrefs)] |
|---|
| 526 |
if projectName is not None: |
|---|
| 527 |
prefs.insert(0, (projectName, self.PreferenceManager)) |
|---|
| 528 |
retFirstTime = False |
|---|
| 529 |
retUpdateNames = [] |
|---|
| 530 |
self._lastCheckInfo = [] |
|---|
| 531 |
for nm, prf in prefs: |
|---|
| 532 |
val = prf.getValue |
|---|
| 533 |
abbrev = self.projectAbbrevs.get(nm.lower()) |
|---|
| 534 |
retFirstTime = (nm == "Dabo") and not prf.hasKey("web_update") |
|---|
| 535 |
retAvailable = False |
|---|
| 536 |
lastcheck = val("last_check") |
|---|
| 537 |
# Store the pref setter and the time so that we can reset the value |
|---|
| 538 |
# in case the update fails later. |
|---|
| 539 |
self._lastCheckInfo.append((prf.setValue, lastcheck)) |
|---|
| 540 |
if not retFirstTime: |
|---|
| 541 |
runCheck = force |
|---|
| 542 |
now = datetime.datetime.now() |
|---|
| 543 |
if not force: |
|---|
| 544 |
webUpdate = val("web_update") |
|---|
| 545 |
if webUpdate: |
|---|
| 546 |
checkInterval = val("update_interval") |
|---|
| 547 |
if checkInterval is None: |
|---|
| 548 |
# Default to one day |
|---|
| 549 |
checkInterval = 24 * 60 |
|---|
| 550 |
mins = datetime.timedelta(minutes=checkInterval) |
|---|
| 551 |
if lastcheck is None: |
|---|
| 552 |
lastcheck = datetime.datetime(1900, 1, 1) |
|---|
| 553 |
runCheck = (now > (lastcheck + mins)) |
|---|
| 554 |
if runCheck: |
|---|
| 555 |
# See if there is a later version |
|---|
| 556 |
url = "http://dabodev.com/frameworkVersions/latest?project=%s" % abbrev |
|---|
| 557 |
try: |
|---|
| 558 |
vers = int(urllib2.urlopen(url).read()) |
|---|
| 559 |
except ValueError: |
|---|
| 560 |
vers = -1 |
|---|
| 561 |
except StandardError, e: |
|---|
| 562 |
dabo.errorLog.write(_("Failed to open URL '%(url)s'. Error: %(e)s") % locals()) |
|---|
| 563 |
localVers = self._currentUpdateVersion(nm) |
|---|
| 564 |
retAvailable = (localVers < vers) |
|---|
| 565 |
prf.setValue("last_check", now) |
|---|
| 566 |
if retFirstTime or retAvailable: |
|---|
| 567 |
retUpdateNames.append(nm) |
|---|
| 568 |
return (retFirstTime, retUpdateNames) |
|---|
| 569 |
|
|---|
| 570 |
|
|---|
| 571 |
def _updateFramework(self, projNames=None): |
|---|
| 572 |
"""Get any changed files from the dabodev.com server, and replace |
|---|
| 573 |
the local copies with them. Return the new revision number""" |
|---|
| 574 |
fileurl = "http://dabodev.com/frameworkVersions/changedFiles/%s/%s" |
|---|
| 575 |
for pn in projNames: |
|---|
| 576 |
currvers = self._currentUpdateVersion(pn) |
|---|
| 577 |
if pn == "Dabo": |
|---|
| 578 |
localBasePath = dabo.frameworkPath |
|---|
| 579 |
else: |
|---|
| 580 |
localBasePath = self._projectInfo[0] |
|---|
| 581 |
abbrev = self.projectAbbrevs[pn.lower()] |
|---|
| 582 |
webpath = self.webUpdateDirs[pn.lower()] |
|---|
| 583 |
try: |
|---|
| 584 |
resp = urllib2.urlopen(fileurl % ( |
|---|