diff options
Diffstat (limited to 'src/frontend/main.py')
-rw-r--r-- | src/frontend/main.py | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/src/frontend/main.py b/src/frontend/main.py new file mode 100644 index 0000000..71e2340 --- /dev/null +++ b/src/frontend/main.py @@ -0,0 +1,350 @@ +""" + + This file is part of the Ventoo program. + + This 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 3 of the License, or + (at your option) any later version. + + It 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 software. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2010 Christopher Harvey + +""" + +import sys +sys.path.append('../backend') +import augeas +import os.path as osp +import pygtk +pygtk.require('2.0') +import gtk +import augeas_utils +import AugFileTree +import VentooModule +import AugEditTree +import shutil +import os +import re +import ErrorDialog +import gtkmozembed + +sandboxDir = '/' + +class MainWindow(gtk.Window): + def __init__(self, augeas): + self.a = augeas + #setup the gui + gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) + self.connect("delete_event", self.close) + self.docBox = gtk.VPaned() + self.rootBox = gtk.VBox() + self.docWindow = gtkmozembed.MozEmbed() + self.docBox.add2(self.docWindow) + self.docWindow.load_url("about:blank") + self.mainToolbar = gtk.Toolbar() + self.diffButton = gtk.ToolButton(None, "Diff!") + self.diffButton.connect("clicked", self.diffPressed, None) + self.showErrorsButton = gtk.ToolButton(None, "Augeas Errors") + self.showErrorsButton.connect("clicked", self.showErrPressed, None) + self.mainToolbar.insert(self.diffButton, -1) + self.mainToolbar.insert(self.showErrorsButton, -1) + self.rootBox.pack_start(self.mainToolbar, False, False) + self.mainPaned = gtk.HPaned() + self.rootBox.pack_start(self.docBox) + self.files_tv = AugFileTree.AugFileTree() + self.edit_tv = AugEditTree.AugEditTree() + self.edit_tv.connect("cursor-changed", self.nodeChanged, None) + self.files_tv_scrolled_window = gtk.ScrolledWindow() + self.files_tv_scrolled_window.add(self.files_tv) + self.files_tv_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) + self.edit_tv_scrolled_window = gtk.ScrolledWindow() + self.edit_tv_scrolled_window.add(self.edit_tv) + self.edit_tv_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) + self.mainPaned.add1(self.files_tv_scrolled_window) + self.mainPaned.add2(self.edit_tv_scrolled_window) + self.docBox.add1(self.mainPaned) + self.add(self.rootBox) + self.files_tv.connect('cursor-changed', self.fileSelectionChanged, None) + self.refreshAugeasFileList() + self.currentModule = None + self.set_default_size(800,600) + self.edit_tv.connect("entry-edited", self.__rowEdited, None) + self.edit_tv.connect("entry-toggled", self.__rowToggled, None) + + """ + A row was enabled or disabled, update augeas tree, then refresh the view. + """ + def __rowToggled(self, editWidget, path, connectData): + model = editWidget.get_model() + thisIter = model.get_iter_from_string(path) + enabled = model.get_value(thisIter, 0) + aug_root = a.get("/augeas/root") + augPath = osp.join('files', osp.relpath(self.currentConfigFilePath, aug_root), self.edit_tv.get_label_path(thisIter)) + if enabled: #this row was just added, update augeas tree. + indexes = path.split(':') + beforeIndex = int(indexes[len(indexes)-1])-1 + if beforeIndex >= 0: + beforePath = "" + for i in indexes[0:len(indexes)-1]: + beforePath = beforePath + str(i) + ":" + beforePath = beforePath + str(beforeIndex) + augBeforePath = self.edit_tv.get_label_path_str(beforePath) + if not aug_root == '/': + augBeforePath = osp.join('files', osp.relpath(self.currentConfigFilePath, aug_root), augBeforePath) + else: + augBeforePath = osp.join('files', augeas_utils.stripBothSlashes(self.currentConfigFilePath), augBeforePath) + self.a.insert(augBeforePath, re.match('^.+/(.+?)(?:\\[[0-9]+\\])?$', augPath).group(1), False) + #print "insert("+augBeforePath+" "+re.match('^.+/(.+?)(?:\\[[0-9]+\\])?$', augPath).group(1)+")" + self.a.set(augPath, '') + else: #this row was deleted, update augeas tree in the refresh + print 'Would remove ' + augPath + self.a.remove(augPath) + self.refreshAugeasEditTree() + + """ + This is called every time a selection in the AST edit window is changed. + This function can be used to update documentation, code complete, finalize changes to the AST + """ + def nodeChanged(self, treeview, user_param1): + p = self.edit_tv.getSelectedEntryPath() + p = augeas_utils.removeNumbers(p) + xmlPath = osp.join("/VentooModule/root", p) + docPath = self.currentModule.getDocURLOf(xmlPath) + if docPath == None: + self.docWindow.load_url("about:blank") + else: + self.docWindow.load_url(docPath) + + """ + Called when a row value (not label, not enabled/disabled) is changed. update augeas tree. + No need to refresh here. + """ + def __rowEdited(self, editWidget, path, text, connectData): + model = editWidget.get_model() + thisIter = model.get_iter_from_string(path) + enabled = model.get_value(thisIter, 0) + aug_root = a.get("/augeas/root") + #given a iter that was edited, and a current file, build the augeas path to the edited value. + if not aug_root == "/": + augPath = osp.join('files', osp.relpath(self.currentConfigFilePath, aug_root), self.edit_tv.get_label_path(thisIter)) + else: + augPath = osp.join('files', augeas_utils.stripBothSlashes(self.currentConfigFilePath), self.edit_tv.get_label_path(thisIter)) + enteredValue = model.get_value(thisIter, 2) #get what the user entered. + #print "Setting " + augPath + " = " + enteredValue + self.a.set(augPath, enteredValue) + + def diffPressed(self, button, data=None): + #show the diff for the current file. + try: + self.a.save() + except IOError: + pass + #TODO: check augeas/files/<file path>/<name>/error + #to be sure the save worked. + augeas_utils.makeDiffTree(self.a, augeas_utils.getDiffRoot()) + diffFiles = [self.currentConfigFilePath, augeas_utils.getDiffLocation(self.a, self.currentConfigFilePath)] + call = "meld " + diffFiles[0] + " " + diffFiles[1] + " &" + print call + if not osp.isfile(diffFiles[1]): + print "Could not find a diff file...were changes made?" + else: + os.system(call) + + def showErrPressed(self, button, data=None): + d = ErrorDialog.ErrorDialog(augeas_utils.getFileErrorList(self.a)) + d.show_all() + d.run() + d.destroy() + + + def close(widget, event, data=None): + gtk.main_quit() + return False + + def refreshAugeasFileList(self): + #reload the file selection list from augeas internals. + self.files_tv.clearFiles() + fileList = augeas_utils.accumulateFiles(a) + for f in fileList: + self.files_tv.addPath(f) + + """ + Given an augeas state and a ventoo module update the edit model to show + that augeas state. + The workhorse for this function is __buildEditModel() + """ + def refreshAugeasEditTree(self): + model = self.edit_tv.get_model() + model.clear() + # aRoot = files/etc/foo for the root of the tree to build. + # sketchy file manipulations, TODO: make this more clear. + tmp = augeas_utils.stripTrailingSlash(self.files_tv.getSelectedConfigFilePath()) + if sandboxDir != '/': + tmp = osp.relpath(self.files_tv.getSelectedConfigFilePath(), sandboxDir) + aRoot = osp.join('files', augeas_utils.stripBothSlashes(tmp)) + # a path into the tree model, coresponds to the aRoot + mRoot = model.get_iter_root() + #path into xml description of the tree + xRoot = '/VentooModule/root' + self.__buildEditModel(model, aRoot, mRoot, xRoot) + + """ + this is the workhorse behind refreshAugeasEditTree() + This is the core function for displaying the editing widget tree. + It can be considered the core of the whole program actually. + This code has to be rock solid. + """ + def __buildEditModel(self, model, augeasFileRoot, modelPathIter, xmlRoot): + xElemRoot = self.currentModule.getChildrenOf(osp.join(xmlRoot, '*')) + xChildren = list(self.currentModule.getChildrenOf(xmlRoot)) + thisMult = self.currentModule.getMultOf(xmlRoot) + if augeas_utils.isDupLevel(self.a, augeasFileRoot): + #this level is just /1 /2 /3, etc... + #for each match + # created = model.append(modelPathIter, [not addedExtra, str(i), '------']) + # if enabled + # self.__buildEditModel(model, osp.join(augeasFileRoot, str(i)), created, xmlRoot) + matches = self.a.match(osp.join(augeasFileRoot, '*')) + have = 0 + maxIndex = 0 + #matches <anything>/<number> and stores <number> as group 1 + indexProg = re.compile('^.+/([0-9]+)/?$') + for match in matches: #add all existing entries + indexResult = indexProg.match(match) + if indexResult != None: #sometimes there are entries on these levels we don't care about. + have += 1 + thisIndex = int(indexResult.group(1)) + maxIndex = max(maxIndex, thisIndex) + created = model.append(modelPathIter, [True, indexResult.group(1), '-------']) + self.__buildEditModel(model, match, created, xmlRoot) + #add the missing entries + numNeeded = augeas_utils.matchDiff(thisMult, have) + #add the required ones. + for i in range(numNeeded): + created = model.append(modelPathIter, [True, osp.join(augeasFileRoot, str(have+i+1)), '-------']) + self.__buildEditModel(model, match, created, xmlRoot) + have += 1 + needOption = not augeas_utils.matchExact(thisMult, have) + if needOption: + created = model.append(modelPathIter, [False, str(have+1), '-------']) + else: + listedNodes = [] #a list of nodes that we already found and know about. + for child in xChildren: + #build get a list of either [child.tag] or [child.tag[1], child.tag[n]] + childMult = self.currentModule.getMultOf(osp.join(xmlRoot, child.tag)) + matches = self.a.match(osp.join(augeasFileRoot, child.tag)) + matches.extend(self.a.match(osp.join(augeasFileRoot, child.tag)+'[*]')) + listedNodes.extend(matches) + + #add leaves if we're missing some required ones (in augeas itself) + have = len(matches) + numNeeded = augeas_utils.matchDiff(childMult, have) + for i in range(have+1, have+numNeeded+1): + p = osp.join(augeasFileRoot, child.tag) + if have+numNeeded > 1: + p = p + '[' + str(i) + ']' + print 'added ' + p + ' to augeas' + self.a.set(p, '') + + #update the matches, since we have added stuff to augeas, based on previous matches + matches = self.a.match(osp.join(augeasFileRoot, child.tag)) + matches.extend(self.a.match(osp.join(augeasFileRoot, child.tag)+'[*]')) + + for match in matches: + userData = self.a.get(match) #add all existing data + if userData == None: + userData = '' + created = model.append(modelPathIter, [True, osp.split(match)[1], userData]) + self.__buildEditModel(model, match, created, osp.join(xmlRoot, child.tag)) + #maybe we need to add more of child to the tree, and maybe even an option for the user. + have = len(matches) + needed = not augeas_utils.matchExact(childMult, have) + numNeeded = augeas_utils.matchDiff(childMult, have) + if needed: + i = 0 + while True: + foo = True + if numNeeded == 0: + foo = False + newLabel = child.tag + if True: + newLabel = newLabel + '['+str(have+i+1)+']' + created = model.append(modelPathIter, [foo, newLabel, '']) + if foo: + self.__buildEditModel(model, 'no_data', created, osp.join(xmlRoot, child.tag)) + i += 1 + if augeas_utils.matchExact(childMult, have+i): + break + if not foo: + break + #now search for and add nodes that haven't been added yet, and may not be in the VentooModule specifically. + allInAugeas = self.a.match(osp.join(augeasFileRoot, '*')) + for a in allInAugeas: + if not a in listedNodes: + #found that 'a' is not in listedNodes, but is in augeasTree, add it, if it is not supposed to be ignored. + if not osp.split(a)[1].startswith('#'): #always ignore comments + userData = self.a.get(a) + created = model.append(modelPathIter, [True, osp.split(a)[1], userData]) + self.__buildEditModel(model, a, created, osp.join(xmlRoot, 'ventoo_dynamic')) + + + """ + Called when the user picks a new file to view. + """ + def fileSelectionChanged(self, tv, data=None): + #uer picked a new file to edit. + self.currentConfigFilePath = self.files_tv.getSelectedConfigFilePath() + #update the display...and get new module info. + #thse path manipulations are sketchy, should make this code clearer. + tmp = self.currentConfigFilePath + if sandboxDir != '/': + tmp = osp.relpath(self.currentConfigFilePath, sandboxDir) + self.currentModule = VentooModule.VentooModule(augeas_utils.getVentooModuleNameFromSysPath(a, tmp)) + self.refreshAugeasEditTree() + +if __name__ == '__main__': + if len(sys.argv) > 1: + sandboxDir = sys.argv[1] + if not osp.isdir(sandboxDir): + print sandboxDir + " is not a directory." + sys.exit(0) + + print 'Starting augeas...' + #None could be a 'loadpath' + a = augeas.Augeas(sandboxDir, None, augeas.Augeas.SAVE_NEWFILE) + print 'Creating window...' + + if sandboxDir == '/': + print """ + +You're running this program on your root directory + This program sometimes has problems with the root directory. + If you're running as root it can't display diff. + if you're not running as root it can't save the system files + (because it doesn't have the permissions to do so. + + Displaying diffs as root will be fixed as soon as I figure out the + right way to do it. + + In the meantime, consider: + cp -r /etc /tmp + python thisProgram.py /tmp + (as non-root)""" + + #Note, it IS possible to create mutiple windows and augeas + #instances to edit multiple "roots" at the same time. + window = MainWindow(a) + window.show_all() + + #clear the diff storage place... + shutil.rmtree(augeas_utils.getDiffRoot(), True) + os.makedirs(augeas_utils.getDiffRoot()) + gtk.main() |