# =========================================================================== # eXe # Copyright 2004-2005, University of Auckland # Copyright 2006-2009 eXe Project, http://eXeLearning.org/ # # 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # =========================================================================== """ This is the main XUL page. """ import os import sys import logging import traceback import shutil from xml.sax.saxutils import escape from twisted.internet import reactor from twisted.web import static from twisted.internet.defer import Deferred from nevow import loaders, inevow, stan from nevow.livepage import handler, js from exe.xului.idevicepane import IdevicePane from exe.xului.outlinepane import OutlinePane from exe.xului.stylemenu import StyleMenu from exe.webui.renderable import RenderableLivePage from exe.xului.propertiespage import PropertiesPage from exe.webui.authoringpage import AuthoringPage from exe.export.websiteexport import WebsiteExport from exe.export.textexport import TextExport from exe.export.singlepageexport import SinglePageExport from exe.export.scormexport import ScormExport from exe.export.imsexport import IMSExport from exe.export.ipodexport import IpodExport from exe.engine.path import Path, toUnicode from exe.engine.package import Package from exe import globals as G from tempfile import mkdtemp from exe.engine.mimetex import compile log = logging.getLogger(__name__) class MainPage(RenderableLivePage): """ This is the main XUL page. Responsible for handling URLs. """ _templateFileName = 'mainpage.xul' name = 'to_be_defined' def __init__(self, parent, package): """ Initialize a new XUL page 'package' is the package that we look after """ self.name = package.name RenderableLivePage.__init__(self, parent, package) self.putChild("resources", static.File(package.resourceDir)) mainxul = Path(self.config.xulDir).joinpath('templates', 'mainpage.xul') self.docFactory = loaders.xmlfile(mainxul) # Create all the children on the left self.outlinePane = OutlinePane(self) self.idevicePane = IdevicePane(self) self.styleMenu = StyleMenu(self) # And in the main section self.authoringPage = AuthoringPage(self) self.propertiesPage = PropertiesPage(self) # translate the "don't close the window" message red_x = _("Please use eXe's\n File... Quit\nmenu to close eXe.") def getChild(self, name, request): """ Try and find the child for the name given """ if name == '': return self else: return super(self, self.__class__).getChild(self, name, request) def goingLive(self, ctx, client): """Called each time the page is served/refreshed""" inevow.IRequest(ctx).setHeader('content-type', 'application/vnd.mozilla.xul+xml') # Set up named server side funcs that js can call def setUpHandler(func, name, *args, **kwargs): """ Convience function link funcs to hander ids and store them """ kwargs['identifier'] = name hndlr = handler(func, *args, **kwargs) hndlr(ctx, client) # Stores it setUpHandler(self.handleIsPackageDirty, 'isPackageDirty') setUpHandler(self.handlePackageFileName, 'getPackageFileName') setUpHandler(self.handleSavePackage, 'savePackage') setUpHandler(self.handleLoadPackage, 'loadPackage') setUpHandler(self.handleLoadRecent, 'loadRecent') setUpHandler(self.handleLoadTutorial, 'loadTutorial') setUpHandler(self.handleClearRecent, 'clearRecent') setUpHandler(self.handleExport, 'exportPackage') setUpHandler(self.handleQuit, 'quit') setUpHandler(self.handleBrowseURL, 'browseURL') setUpHandler(self.handleInsertPackage, 'insertPackage') setUpHandler(self.handleExtractPackage, 'extractPackage') setUpHandler(self.outlinePane.handleSetTreeSelection, 'setTreeSelection') setUpHandler(self.handleClearAndMakeTempPrintDir, 'makeTempPrintDir') setUpHandler(self.handleRemoveTempDir, 'removeTempDir') setUpHandler(self.handleTinyMCEimageChoice, 'previewTinyMCEimage') setUpHandler(self.handleTinyMCEmath, 'generateTinyMCEmath') setUpHandler(self.handleTestPrintMsg, 'testPrintMessage') setUpHandler(self.handleSetLocale, 'setLocale') setUpHandler(self.handleSetInternalAnchors, 'setInternalAnchors') self.idevicePane.client = client # Render the js handleId = "'", client.handleId, "'" def render_mainMenu(self, ctx, data): """Mac menubars are not shown so make it a toolbar""" if sys.platform[:6] == "darwin": ctx.tag.tagName = 'toolbar' return ctx.tag def render_addChild(self, ctx, data): """Fills in the oncommand handler for the add child button and short cut key""" return ctx.tag(oncommand=handler(self.outlinePane.handleAddChild, js('currentOutlineId()'))) def render_delNode(self, ctx, data): """Fills in the oncommand handler for the delete child button and short cut key""" return ctx.tag(oncommand=handler(self.outlinePane.handleDelNode, js("confirmDelete()"), js('currentOutlineId()'))) def render_renNode(self, ctx, data): """Fills in the oncommand handler for the rename node button and short cut key""" return ctx.tag(oncommand=handler(self.outlinePane.handleRenNode, js('currentOutlineId()'), js('askNodeName()'), bubble=True)) def render_prePath(self, ctx, data): """Fills in the package name to certain urls in the xul""" request = inevow.IRequest(ctx) return ctx.tag(src=self.package.name + '/' + ctx.tag.attributes['src']) # The node moving buttons def _passHandle(self, ctx, name): """Ties up a handler for the promote, demote, up and down buttons. (Called by below funcs)""" attr = getattr(self.outlinePane, 'handle%s' % name) return ctx.tag(oncommand=handler(attr, js('currentOutlineId()'))) def render_promote(self, ctx, data): """Fills in the oncommand handler for the Promote button and shortcut key""" return self._passHandle(ctx, 'Promote') def render_demote(self, ctx, data): """Fills in the oncommand handler for the Demote button and shortcut key""" return self._passHandle(ctx, 'Demote') def render_up(self, ctx, data): """Fills in the oncommand handler for the Up button and shortcut key""" return self._passHandle(ctx, 'Up') def render_down(self, ctx, data): """Fills in the oncommand handler for the Down button and shortcut key""" return self._passHandle(ctx, 'Down') def render_recentProjects(self, ctx, data): """ Fills in the list of recent projects menu """ result = ['\n'] for num, path in enumerate(self.config.recentProjects): result.append(' ' % {'num': num + 1, 'path': escape(path)}) result.append(' ') result.append(' ' % _('Clear Recent Projects List')) result.append('') return stan.xml('\n'.join(result)) def render_debugInfo(self, ctx, data): """Renders debug info to the to of the screen if logging is set to debug level """ if log.getEffectiveLevel() == logging.DEBUG: # TODO: Needs to be updated by xmlhttp or xmlrpc request = inevow.IRequest(ctx) return stan.xml(('\n' ' \n' ' \n' '\n' % ([escape(x) for x in request.prepath], escape(self.package.name)))) else: return '' # Handle Methods # purely a quick test method for printing from the client->server: def handleTestPrintMsg(self, client, message): """ Prints a test message, and yup, that's all! """ print "Test Message: ", message, " [eol, eh!]" def handleIsPackageDirty(self, client, ifClean, ifDirty): """ Called by js to know if the package is dirty or not. ifClean is JavaScript to be evaled on the client if the package has been changed ifDirty is JavaScript to be evaled on the client if the package has not been changed """ if self.package.isChanged: client.sendScript(ifDirty) else: client.sendScript(ifClean) def handlePackageFileName(self, client, onDone, onDoneParam): """ Calls the javascript func named by 'onDone' passing as the only parameter the filename of our package. If the package has never been saved or loaded, it passes an empty string 'onDoneParam' will be passed to onDone as a param after the filename """ client.call(onDone, unicode(self.package.filename), onDoneParam) def b4save(self, client, inputFilename, ext, msg): """ Call this before saving a file to get the right filename. Returns a new filename or 'None' when attempt to overide 'inputFilename' is the filename given by the user 'ext' is the extension that the filename should have 'msg' will be shown if the filename already exists """ if not inputFilename.lower().endswith(ext): inputFilename += ext if Path(inputFilename).exists(): explanation = _(u'"%s" already exists.\nPlease try again with a different filename') % inputFilename msg = u'%s\n%s' % (msg, explanation) client.alert(msg) raise Exception(msg) return inputFilename def handleSavePackage(self, client, filename=None, onDone=None): """ Save the current package 'filename' is the filename to save the package to 'onDone' will be evaled after saving instead or redirecting to the new location (in cases of package name changes). (This is used where the user goes file|open when their package is changed and needs saving) """ filename = Path(filename) saveDir = filename.dirname() if saveDir and not saveDir.isdir(): client.alert(_(u'Cannot access directory named ') + unicode(saveDir) + _(u'. Please use ASCII names.')) return oldName = self.package.name # If the script is not passing a filename to us, # Then use the last filename that the package was loaded from/saved to if not filename: filename = self.package.filename assert filename, 'Somehow save was called without a filename on a package that has no default filename.' # Add the extension if its not already there and give message if not saved filename = self.b4save(client, filename, '.elp', _(u'SAVE FAILED!')) try: self.package.save(filename) # This can change the package name except Exception, e: client.alert(_('SAVE FAILED!\n%s' % str(e))) raise # Tell the user and continue client.alert(_(u'Package saved to: %s' % filename)) if onDone: client.sendScript(onDone) elif self.package.name != oldName: # Redirect the client if the package name has changed self.webServer.root.putChild(self.package.name, self) log.info('Package saved, redirecting client to /%s' % self.package.name) client.sendScript('top.location = "/%s"' % self.package.name.encode('utf8')) def handleLoadPackage(self, client, filename): """Load the package named 'filename'""" package = self._loadPackage(client, filename, newLoad=True) packageStore = self.webServer.application.packageStore packageStore.addPackage(package) self.root.bindNewPackage(package) client.sendScript((u'top.location = "/%s"' % \ package.name).encode('utf8')) def handleLoadRecent(self, client, number): """ Loads a file from our recent files list """ filename = self.config.recentProjects[int(number) - 1] self.handleLoadPackage(client, filename) def handleLoadTutorial(self, client): """ Loads the tutorial file, from the Help menu """ filename = self.config.webDir.joinpath("docs")\ .joinpath("eXe-tutorial.elp") self.handleLoadPackage(client, filename) def handleClearRecent(self, client): """ Clear the recent project list """ G.application.config.recentProjects = [] G.application.config.configParser.write() # rerender the menus client.sendScript('top.location = "/%s"' % self.package.name.encode('utf8')) def handleSetLocale(self, client, locale): """ Set locale using Nevow instead of a POST """ G.application.config.locale = locale G.application.config.locales[locale].install(unicode=True) G.application.config.configParser.set('user', 'locale', locale) client.sendScript((u'top.location = "/%s"' % \ self.package.name).encode('utf8')) def handleSetInternalAnchors(self, client, internalAnchors): """ Set locale using Nevow instead of a POST """ G.application.config.internalAnchors = internalAnchors G.application.config.configParser.set('user', 'internalAnchors', internalAnchors) client.sendScript((u'top.location = "/%s"' % \ self.package.name).encode('utf8')) def handleRemoveTempDir(self, client, tempdir, rm_top_dir): """ Removes a temporary directory and any contents therein (from the bottom up), and yup, that's all! """ # # swiped from an example on: # http://docs.python.org/lib/os-file-dir.html top = tempdir ################################################################ # Delete everything reachable from the directory named in 'top', # assuming there are no symbolic links. # CAUTION: This is dangerous! For example, if top == '/', it # could delete all your disk files. for root, dirs, files in os.walk(top, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) ################################################################## # and finally, go ahead and remove the top-level tempdir itself: if (int(rm_top_dir) != 0): os.rmdir(tempdir) def get_printdir_relative2web(self, exported_dir): """ related to the following ClearParentTempPrintDirs(), return a local URL corresponding to the exported_dir """ rel_name = exported_dir[len(G.application.tempWebDir):] if sys.platform[:3] == "win": rel_name = rel_name.replace('\\', '/') if rel_name.startswith('/'): rel_name = rel_name[1:] http_relative_pathname = "http://127.0.0.1:" + str(self.config.port) \ + '/' + rel_name log.debug('printdir http_relative_pathname=' + http_relative_pathname) return http_relative_pathname def ClearParentTempPrintDirs(self, client, log_dir_warnings): """ Determine the parent temporary printing directory, and clear them if safe to do so (i.e., if not the config dir itself, for example) Makes (if necessary), and clears out (if applicable) the parent temporary directory. The calling handleClearAndMakeTempPrintDir() shall then make a specific print-job subdirectory. """ # # Create the parent temp print dir as hardcoded under the webdir, as: # http://temp_print_dirs # (eventually may want to allow this information to be configured by # the user, stored in globals, etc.) web_dirname = G.application.tempWebDir under_dirname = os.path.join(web_dirname,"temp_print_dirs") clear_tempdir = 0 dir_warnings = "" # but first need to ensure that under_dirname itself is available; # if not, create it: if cmp(under_dirname,"") != 0: if os.path.exists(under_dirname): if (os.path.isdir(under_dirname)): # Yes, this directory already exists. # pre-clean it, keeping the clutter down: clear_tempdir = 1 else: dir_warnings = "WARNING: The desired Temporary Print " \ + "Directory, \"" + under_dirname \ + "\", already exists, but as a file!\n" if log_dir_warnings: log.warn("ClearParentTempPrintDirs(): The desired " \ + "Temporary Print Directory, \"%s\", " \ + "already exists, but as a file!", \ under_dirname) under_dirname = web_dirname # but, we can't just put the tempdirs directly underneath # the webDir, since no server object exists for it. # So, as a quick and dirty solution, go ahead and put # them in the images folder: under_dirname = os.path.join(under_dirname,"images") dir_warnings += " RECOMMENDATION: please " \ + "remove/rename this file to allow eXe easier "\ + "management of its temporary print files.\n" dir_warnings += " eXe will create the temporary " \ + "printing directory directly under \"" \ + under_dirname + "\" instead, but this might "\ +"leave some files around after eXe terminates..." if log_dir_warnings: log.warn(" RECOMMENDATION: please remove/rename "\ + "this file to allow eXe easier management of "\ + "its temporary print files.") log.warn(" eXe will create the temporary " \ + "printing directory directly under \"%s\" " \ + "instead, but this might leave some files " \ + "around after eXe terminates...", \ under_dirname) # and note that we do NOT want to clear_tempdir # on the config dir itself!!!!! else: os.makedirs(under_dirname) # and while we could clear_tempdir on it, there's no need to. if clear_tempdir : # before making this particular print job's temporary print # directory underneath the now-existing temp_print_dirs, # go ahead and clear out temp_print_dirs such that we have # AT MOST one old temporary set of print job files still existing # once eXe terminates: rm_topdir = "0" # note: rm_topdir is passed in as a STRING since # handleRemoveTempDir expects as such from nevow's # clientToServerEvent() call: self.handleRemoveTempDir(client, under_dirname, rm_topdir) return under_dirname, dir_warnings def handleClearAndMakeTempPrintDir(self, client, suffix, prefix, \ callback): """ Makes a temporary printing directory, and yup, that's pretty much it! """ # First get the name of the parent temp directory, after making it # (if necessary) and clearing (if applicable): log_dir_warnings = 1 (under_dirname, dir_warnings) = self.ClearParentTempPrintDirs( \ client, log_dir_warnings) # Next, go ahead and create this particular print job's temporary # directory under the parent temp directory: temp_dir = mkdtemp(suffix, prefix, under_dirname) # Finally, pass the created temp_dir back to the expecting callback: client.call(callback, temp_dir, dir_warnings) def handleTinyMCEimageChoice(self, client, tinyMCEwin, tinyMCEwin_name, \ tinyMCEfield, local_filename, preview_filename): """ Once an image is selected in the file browser that is spawned by the TinyMCE image dialog, copy this file (which is local to the user's machine) into the server space, under a preview directory (after checking if this exists, and creating it if necessary). Note that this IS a "cheat", in violation of the client-server separation, but can be done since we know that the eXe server is actually sitting on the client host. """ server_filename = "" callback_errors = "" errors = 0 log.debug('handleTinyMCEimageChoice: image local = ' + local_filename + ', base=' + os.path.basename(local_filename)) webDir = Path(G.application.tempWebDir) previewDir = webDir.joinpath('previews') if not previewDir.exists(): log.debug("image previews directory does not yet exist; " \ + "creating as %s " % previewDir) previewDir.makedirs() elif not previewDir.isdir(): client.alert( \ _(u'Preview directory %s is a file, cannot replace it') \ % previewDir) log.error("Couldn't preview tinyMCE-chosen image: "+ "Preview dir %s is a file, cannot replace it" \ % previewDir) callback_errors = "Preview dir is a file, cannot replace" errors += 1 if errors == 0: log.debug('handleTinyMCEimageChoice: originally, local_filename=' + local_filename) local_filename = unicode(local_filename, 'utf-8') log.debug('handleTinyMCEimageChoice: in unicode, local_filename=' + local_filename) localImagePath = Path(local_filename) log.debug('handleTinyMCEimageChoice: after Path, localImagePath= ' + localImagePath); if not localImagePath.exists() or not localImagePath.isfile(): client.alert( \ _(u'Local file %s is not found, cannot preview it') \ % localImagePath) log.error("Couldn't find tinyMCE-chosen image: %s" \ % localImagePath) callback_errors = "Image file %s not found, cannot preview" \ % localImagePath errors += 1 try: # joinpath needs its join arguments to already be in Unicode: #preview_filename = toUnicode(preview_filename); # but that's okay, cuz preview_filename is now URI safe, right? log.debug('URIencoded preview filename=' + preview_filename); server_filename = previewDir.joinpath(preview_filename); log.debug("handleTinyMCEimageChoice copying image from \'"\ + local_filename + "\' to \'" \ + server_filename.abspath() + "\'."); shutil.copyfile(local_filename, \ server_filename.abspath()); # new optional description file to provide the # actual base filename, such that once it is later processed # copied into the resources directory, it can be done with # only the basename. Otherwise the resource filenames # are too long for some users, preventing them from making # backup CDs of the content, for example. # # Remember that the full path of the # file is only used here as an easy way to keep the names # unique WITHOUT requiring a roundtrip call from the Javascript # to this server, and back again, a process which does not # seem to work with tinyMCE in the mix. BUT, once tinyMCE's # part is done, and this image processed, it can be returned # to just its basename, since the resource parts have their # own unique-ification mechanisms already in place. descrip_file_path = Path(server_filename+".exe_info") log.debug("handleTinyMCEimageChoice creating preview " \ + "description file \'" \ + descrip_file_path.abspath() + "\'."); descrip_file = open(descrip_file_path, 'wb') # safety measures against TinyMCE, otherwise it will # later take ampersands and entity-escape them into '&', # and filenames with hash signs will not be found, etc.: unspaced_filename = local_filename.replace(' ','_') unhashed_filename = unspaced_filename.replace('#', '_num_') unamped_local_filename = unhashed_filename.replace('&', '_and_') log.debug("and setting new file basename as: " + unamped_local_filename); my_basename = os.path.basename(unamped_local_filename) descrip_file.write((u"basename="+my_basename).encode('utf-8')) descrip_file.flush() descrip_file.close() except Exception, e: client.alert(_('SAVE FAILED!\n%s' % str(e))) log.error("handleTinyMCEimageChoice unable to copy local image "\ +"file to server prevew, error = " + str(e)) raise def handleTinyMCEmath(self, client, tinyMCEwin, tinyMCEwin_name, \ tinyMCEfield, latex_source, math_fontsize, \ preview_image_filename, preview_math_srcfile): """ Based off of handleTinyMCEimageChoice(), handleTinyMCEmath() is similar in that it places a .gif math image (and a corresponding .tex LaTeX source file) into the previews dir. Rather than copying the image from a user-selected directory, though, this routine actually generates the math image using mimetex. """ server_filename = "" callback_errors = "" errors = 0 webDir = Path(G.application.tempWebDir) previewDir = webDir.joinpath('previews') if not previewDir.exists(): log.debug("image previews directory does not yet exist; " \ + "creating as %s " % previewDir) previewDir.makedirs() elif not previewDir.isdir(): client.alert( \ _(u'Preview directory %s is a file, cannot replace it') \ % previewDir) log.error("Couldn't preview tinyMCE-chosen image: "+ "Preview dir %s is a file, cannot replace it" \ % previewDir) callback_errors = "Preview dir is a file, cannot replace" errors += 1 #if errors == 0: # localImagePath = Path(local_filename) # if not localImagePath.exists() or not localImagePath.isfile(): # client.alert( \ # _(u'Image file %s is not found, cannot preview it') \ # % localImagePath) # log.error("Couldn't find tinyMCE-chosen image: %s" \ # % localImagePath) # callback_errors = "Image file %s not found, cannot preview" \ # % localImagePath # errors += 1 # the mimetex usage code was swiped from the Math iDevice: if latex_source <> "": # first write the latex_source out into the preview_math_srcfile, # such that it can then be passed into the compile command: math_filename = previewDir.joinpath(preview_math_srcfile) math_filename_str = math_filename.abspath().encode('utf-8') log.info("handleTinyMCEmath: using LaTeX source: " + latex_source) log.debug("writing LaTeX source into \'" \ + math_filename_str + "\'.") math_file = open(math_filename, 'wb') # do we need to append a \n here?: math_file.write(latex_source) math_file.flush() math_file.close() try: use_latex_sourcefile = math_filename_str tempFileName = compile(use_latex_sourcefile, math_fontsize, \ latex_is_file=True) except Exception, e: client.alert(_('MimeTeX compile failed!\n%s' % str(e))) log.error("handleTinyMCEmath unable to compile LaTeX using "\ +"mimetex, error = " + str(e)) raise # copy the file into previews server_filename = previewDir.joinpath(preview_image_filename); log.debug("handleTinyMCEmath copying math image from \'"\ + tempFileName + "\' to \'" \ + server_filename.abspath().encode('utf-8') + "\'."); shutil.copyfile(tempFileName, \ server_filename.abspath().encode('utf-8')); # Delete the temp file made by compile Path(tempFileName).remove() return def handleExport(self, client, exportType, filename, print_callback=''): """ Called by js. Exports the current package to one of the above formats 'exportType' can be one of 'singlePage' 'webSite' 'zipFile' 'ipod' 'textFile' or 'scorm' 'filename' is a file for scorm pages, and a directory for websites """ webDir = Path(self.config.webDir) stylesDir = webDir.joinpath('style', self.package.style) exportDir = Path(filename).dirname() if exportDir and not exportDir.exists(): client.alert(_(u'Cannot access directory named ') + unicode(exportDir) + _(u'. Please use ASCII names.')) return """ adding the print feature in using the same export functionality: """ if exportType == 'singlePage' or exportType == 'printSinglePage': printit = 0 if exportType == 'printSinglePage': printit = 1 exported_dir = self.exportSinglePage(client, filename, webDir, \ stylesDir, printit) # the above will return None if the desired exported directory # already exists (printing always goes to a new temp dir, though): if printit == 1 and not exported_dir is None: web_printdir = self.get_printdir_relative2web(exported_dir) # now that this has ben exported, go ahead and trigger # the requested printing callback: client.call(print_callback, filename, exported_dir, \ web_printdir) elif exportType == 'webSite': self.exportWebSite(client, filename, stylesDir) elif exportType == 'zipFile': filename = self.b4save(client, filename, '.zip', _(u'EXPORT FAILED!')) self.exportWebZip(client, filename, stylesDir) elif exportType == 'textFile': self.exportText(client, filename) elif exportType == 'ipod': self.exportIpod(client, filename) elif exportType == "scorm": filename = self.b4save(client, filename, '.zip', _(u'EXPORT FAILED!')) self.exportScorm(client, filename, stylesDir, "scorm1.2") elif exportType == "scorm2004": filename = self.b4save(client, filename, '.zip', _(u'EXPORT FAILED!')) self.exportScorm(client, filename, stylesDir, "scorm2004") elif exportType == "commoncartridge": filename = self.b4save(client, filename, '.zip', _(u'EXPORT FAILED!')) self.exportScorm(client, filename, stylesDir, "commoncartridge") else: filename = self.b4save(client, filename, '.zip', _(u'EXPORT FAILED!')) self.exportIMS(client, filename, stylesDir) def handleQuit(self, client): """ Stops the server """ # first, go ahead and clear out any temp job files still in # the temporary print directory: log_dir_warnings = 0 # don't warn of any issues with the directories at quit, # since already warned at initial directory creation (parent_temp_print_dir, dir_warnings) = \ self.ClearParentTempPrintDirs(client, log_dir_warnings) reactor.stop() def handleBrowseURL(self, client, url): """visit the specified URL using the system browser if the URL contains %s, substitute the local webDir if the URL contains %t, show a temp file containing NEWS and README """ if url.find('%t') > -1: release_notes = os.path.join(G.application.tempWebDir, 'Release_Notes.html') f = open(release_notes, 'wt') f.write('''eXe Release Notes

News

\n''')
            try:
                news = open(os.path.join(self.config.webDir, 'NEWS'),
                        'rt').read()
                readme = open(os.path.join(self.config.webDir, 'README'),
                        'rt').read()
                f.write(news)
                f.write('

Read Me

\n')
                f.write(readme)
            except IOError:
                # fail silently if we can't read either of the files
                pass
            f.write('
') f.close() url = url.replace('%t', release_notes) else: url = url.replace('%s', self.config.webDir) log.debug(u'browseURL: ' + url) if hasattr(os, 'startfile'): os.startfile(url) elif sys.platform[:6] == "darwin": import webbrowser webbrowser.open(url, new=True) else: os.system("firefox " + url + "&") def handleInsertPackage(self, client, filename): """ Load the package and insert in current node """ loadedPackage = self._loadPackage(client, filename, newLoad=False, destinationPackage=self.package) newNode = loadedPackage.root.copyToPackage(self.package, self.package.currentNode) # trigger a rename of all of the internal nodes and links, # and to add any such anchors into the dest package via isMerge: newNode.RenamedNodePath(isMerge=True) client.sendScript((u'top.location = "/%s"' % \ self.package.name).encode('utf8')) def handleExtractPackage(self, client, filename, existOk): """ Create a new package consisting of the current node and export 'existOk' means the user has been informed of existance and ok'd it """ filename = Path(filename) saveDir = filename.dirname() if saveDir and not saveDir.exists(): client.alert(_(u'Cannot access directory named ') + unicode(saveDir) + _(u'. Please use ASCII names.')) return # Add the extension if its not already there if not filename.lower().endswith('.elp'): filename += '.elp' if Path(filename).exists() and existOk != 'true': msg = _(u'"%s" already exists.\nPlease try again with a different filename') % filename client.alert(_(u'EXTRACT FAILED!\n%s' % msg)) return try: # Create a new package for the extracted nodes newPackage = self.package.extractNode() # trigger a rename of all of the internal nodes and links, # and to remove any old anchors from the dest package, # and remove any zombie links via isExtract: newNode = newPackage.root if newNode: newNode.RenamedNodePath(isExtract=True) # Save the new package newPackage.save(filename) except Exception, e: client.alert(_('EXTRACT FAILED!\n%s' % str(e))) raise client.alert(_(u'Package extracted to: %s' % filename)) # Public Methods def exportSinglePage(self, client, filename, webDir, stylesDir, \ printFlag): """ Export 'client' to a single web page, 'webDir' is just read from config.webDir 'stylesDir' is where to copy the style sheet information from 'printFlag' indicates whether or not this is for print (and whatever else that might mean) """ try: imagesDir = webDir.joinpath('images') scriptsDir = webDir.joinpath('scripts') templatesDir = webDir.joinpath('templates') # filename is a directory where we will export the website to # We assume that the user knows what they are doing # and don't check if the directory is already full or not # and we just overwrite what's already there filename = Path(filename) # Append the package name to the folder path if necessary if filename.basename() != self.package.name: filename /= self.package.name if not filename.exists(): filename.makedirs() elif not filename.isdir(): client.alert(_(u'Filename %s is a file, cannot replace it') % filename) log.error("Couldn't export web page: "+ "Filename %s is a file, cannot replace it" % filename) return else: client.alert(_(u'Folder name %s already exists. ' 'Please choose another one or delete existing one then try again.') % filename) return # Now do the export singlePageExport = SinglePageExport(stylesDir, filename, \ imagesDir, scriptsDir, templatesDir) singlePageExport.export(self.package, printFlag) except Exception, e: client.alert(_('SAVE FAILED!\n%s' % str(e))) raise # Show the newly exported web site in a new window if not printFlag: self._startFile(filename) # and return a string of the actual directory name, # in case the package name was added, etc.: return filename.abspath().encode('utf-8') # WARNING: the above only returns the RELATIVE pathname def exportWebSite(self, client, filename, stylesDir): """ Export 'client' to a web site, 'webDir' is just read from config.webDir 'stylesDir' is where to copy the style sheet information from """ try: # filename is a directory where we will export the website to # We assume that the user knows what they are doing # and don't check if the directory is already full or not # and we just overwrite what's already there filename = Path(filename) # Append the package name to the folder path if necessary if filename.basename() != self.package.name: filename /= self.package.name if not filename.exists(): filename.makedirs() elif not filename.isdir(): client.alert(_(u'Filename %s is a file, cannot replace it') % filename) log.error("Couldn't export web page: "+ "Filename %s is a file, cannot replace it" % filename) return else: client.alert(_(u'Folder name %s already exists. ' 'Please choose another one or delete existing one then try again.') % filename) return # Now do the export websiteExport = WebsiteExport(self.config, stylesDir, filename) websiteExport.export(self.package) except Exception, e: client.alert(_('EXPORT FAILED!\n%s') % str(e)) raise # Show the newly exported web site in a new window self._startFile(filename) def exportWebZip(self, client, filename, stylesDir): try: log.debug(u"exportWebsite, filename=%s" % filename) filename = Path(filename) # Do the export filename = self.b4save(client, filename, '.zip', _(u'EXPORT FAILED!')) websiteExport = WebsiteExport(self.config, stylesDir, filename) websiteExport.exportZip(self.package) except Exception, e: client.alert(_('EXPORT FAILED!\n%s' % str(e))) raise client.alert(_(u'Exported to %s') % filename) def exportText(self, client, filename): try: filename = Path(filename) log.debug(u"exportWebsite, filename=%s" % filename) # Append an extension if required if not filename.lower().endswith('.txt'): filename += '.txt' if Path(filename).exists(): msg = _(u'"%s" already exists.\nPlease try again with a different filename') % filename client.alert(_(u'EXPORT FAILED!\n%s' % msg)) return # Do the export textExport = TextExport(filename) textExport.export(self.package) except Exception, e: client.alert(_('EXPORT FAILED!\n%s' % str(e))) raise client.alert(_(u'Exported to %s') % filename) def exportIpod(self, client, filename): """ Export 'client' to an iPod Notes folder tree 'webDir' is just read from config.webDir """ try: # filename is a directory where we will export the notes to # We assume that the user knows what they are doing # and don't check if the directory is already full or not # and we just overwrite what's already there filename = Path(filename) # Append the package name to the folder path if necessary if filename.basename() != self.package.name: filename /= self.package.name if not filename.exists(): filename.makedirs() elif not filename.isdir(): client.alert(_(u'Filename %s is a file, cannot replace it') % filename) log.error("Couldn't export web page: "+ "Filename %s is a file, cannot replace it" % filename) return else: client.alert(_(u'Folder name %s already exists. ' 'Please choose another one or delete existing one then try again.') % filename) return # Now do the export ipodExport = IpodExport(self.config, filename) ipodExport.export(self.package) except Exception, e: client.alert(_('EXPORT FAILED!\n%s') % str(e)) raise client.alert(_(u'Exported to %s') % filename) def exportScorm(self, client, filename, stylesDir, scormType): """ Exports this package to a scorm package file """ try: filename = Path(filename) log.debug(u"exportScorm, filename=%s" % filename) # Append an extension if required if not filename.lower().endswith('.zip'): filename += '.zip' if Path(filename).exists(): msg = _(u'"%s" already exists.\nPlease try again with a different filename') % filename client.alert(_(u'EXPORT FAILED!\n%s' % msg)) return # Do the export scormExport = ScormExport(self.config, stylesDir, filename, scormType) scormExport.export(self.package) except Exception, e: client.alert(_('EXPORT FAILED!\n%s' % str(e))) raise client.alert(_(u'Exported to %s') % filename) def exportIMS(self, client, filename, stylesDir): """ Exports this package to a ims package file """ try: log.debug(u"exportIMS") # Append an extension if required if not filename.lower().endswith('.zip'): filename += '.zip' if Path(filename).exists(): msg = _(u'"%s" already exists.\nPlease try again with a different filename') % filename client.alert(_(u'EXPORT FAILED!\n%s' % msg)) return # Do the export imsExport = IMSExport(self.config, stylesDir, filename) imsExport.export(self.package) except Exception, e: client.alert(_('EXPORT FAILED!\n%s' % str(e))) raise client.alert(_(u'Exported to %s' % filename)) # Utility methods def _startFile(self, filename): """ Launches an exported web site or page """ if hasattr(os, 'startfile'): try: os.startfile(filename) except UnicodeEncodeError: os.startfile(filename.encode(Path.fileSystemEncoding)) elif sys.platform[:6] == "darwin": import webbrowser filename /= 'index.html' webbrowser.open('file://'+filename) else: filename /= 'index.html' log.debug(u"firefox file://"+filename+"&") os.system("firefox file://"+filename+"&") def _loadPackage(self, client, filename, newLoad=True, destinationPackage=None): """Load the package named 'filename'""" try: encoding = sys.getfilesystemencoding() if encoding is None: encoding = 'utf-8' filename2 = toUnicode(filename, encoding) log.debug("filename and path" + filename2) # see if the file exists AND is readable by the user try: open(filename2, 'rb').close() except IOError: filename2 = toUnicode(filename, 'utf-8') try: open(filename2, 'rb').close() except IOError: client.alert(_(u'File %s does not exist or is not readable.') % filename2) return None package = Package.load(filename2, newLoad, destinationPackage) if package is None: raise Exception(_("Couldn't load file, please email file to bugs@exelearning.org")) except Exception, exc: if log.getEffectiveLevel() == logging.DEBUG: client.alert(_(u'Sorry, wrong file format:\n%s') % unicode(exc)) else: client.alert(_(u'Sorry, wrong file format')) log.error(u'Error loading package "%s": %s' % (filename2, unicode(exc))) log.error(u'Traceback:\n%s' % traceback.format_exc()) raise return package