diff options
Diffstat (limited to 'build_tools/larch7/larch0/gui/controller.py')
-rw-r--r-- | build_tools/larch7/larch0/gui/controller.py | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/build_tools/larch7/larch0/gui/controller.py b/build_tools/larch7/larch0/gui/controller.py new file mode 100644 index 0000000..2025301 --- /dev/null +++ b/build_tools/larch7/larch0/gui/controller.py @@ -0,0 +1,466 @@ +#!/usr/bin/env python +# +# controller.py - Manages file-system access and calling of larch scripts +# +# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com) +# +# This file is part of the larch project. +# +# larch 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. +# +# larch 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 larch; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#---------------------------------------------------------------------------- +#2010.07.19 + +import sys, os, pwd, traceback, __builtin__ +try: + import json as serialize +except: + import simplejson as serialize +from subprocess import Popen, PIPE, call +import threading +import re +import SocketServer + +from config import * +start_translator(switchC=False) + + +exports = {} +def add_exports(elist): + for key, method in elist: + exports[key] = method + +__builtin__.add_exports = add_exports + + +def error0(message): + sys.stderr.write('>>ERROR>>' + message + '\n') + sys.stderr.flush() +__builtin__.error0 = error0 + + +class Fs: + """Collect file system access methods in one class. + """ + def __init__(self): + add_exports( ( + ('fetch_layout', self.fetch_layout), + ('isfile', self.isfile), + ('isdir', self.isdir), + ('rm_rf', self.rm_rf), + ('get_partitions', self.get_partitions), + ('readfile', self.readfile), + ('savefile', self.savefile), + ('get_docs_url', self.get_docs_url), + ('oldsqf', self.oldsqf), + ('oldlocales', self.oldlocales), +# ('getpath', self.getpath), + ('browse', self.browse)) + ) + + def fetch_layout(self, layout_file): + fh = open(base_dir + '/gui/layouts/' + layout_file) + r = fh.read() + fh.close() + return (True, eval(r)) + + def rm_rf(self, path): + call(['rm', '-rf', self._getpath(path)]) + return (True, None) + + +# def exists(self, path): +# return os.path.exists(self._getpath(path)) + +# def copy(self, src, dst): +# shutil.copytree(self._getpath(src), self._getpath(dst)) + + + def oldsqf(self): + return (True, + os.path.isfile(self._getpath('install:' + CHROOT_SYSTEMSQF)) + or os.path.isfile(self._getpath('install:' + CHROOT_DIR_MEDIUM + + '/larch/system.sqf'))) + + def oldlocales(self): + return (True, os.path.isdir(self._getpath('install:%s/locale' + % CHROOT_DIR_BUILD))) + + def isfile(self, path): + return (True, os.path.isfile(self._getpath(path))) + + def isdir(self, path): + return (True, os.path.isdir(self._getpath(path))) + + + def browse(self, path): + fpath = self._getpath(path) + if call(['mkdir', '-p', fpath]) == 0: + # Start file browser at fpath + call(project_manager.appget('filebrowser').replace('$', fpath) + + ' &', shell=True) + return (True, None) + else: + return (False, None) + + +# def makedirs(self, path): +# os.makedirs(self._getpath(path)) +# return (True, None) + + def readfile(self, f): + f = self._getpath(f) + try: + fh = open(f) + r = fh.read() + fh.close() + return (True, r) + except: + return (False, _("Couldn't read file '%s'") % f) + + def savefile(self, f, d): + f = self._getpath(f) + dir = os.path.dirname(f) + if not os.path.isdir(dir): + os.makedirs(dir) + try: + fh = open(f, "w") + fh.write(d) + fh.close() + return (True, None) + except: + return (False, _("Couldn't save file '%s'") % f) + + def _getpath(self, f): + if f[0] != "/": + base, f = f.split(':') + f = '/' + f + if base == 'base': + f = base_dir + f + elif base == 'profile': + f = project_manager.profile_path + f + elif base == 'working': + f = project_manager.project_dir + f + else: + f = project_manager.get_ipath()[1] + f + return f + + def get_docs_url(self, page): + if lang and (len(lang) > 1): + p = base_dir + ('/docs/%s/html/' % lang[0:2]) + page + if os.path.isfile(p): + return (True, p) + return (True, base_dir + '/docs/html/' + page) + + + def get_partitions(self): + """Get a list of available partitions (only unmounted ones + are included). + """ + # First get a list of mounted devices + mounteds = [] + fh = open('/etc/mtab') + for l in fh: + dev = l.split()[0] + if dev.startswith('/dev/sd'): + mounteds.append(dev[5:]) + fh.close() + # Get a list of partitions + partlist = [] + fh = open('/proc/partitions') + for l in fh: + fields = l.split() + if len(fields) == 4: + dev = fields[3] + if dev.startswith('sd') and (dev[-1] in '0123456789'): + size = (int(fields[2]) + 512) / 1024 + if (size > 200) and (dev not in mounteds): + # Keep a tuple (partition, size in MiB) + partlist.append("%-12s %12s MiB" + % ('/dev/' + dev, size)) + fh.close() + return (True, partlist) + + + +class LarchScripts: + """This class deals with calling the larch scripts. + As they must be run as root a separate dispatcher process, running as + root, is used to call the actual scripts. The dispatcher is started + using 'sudo'. + A call will be initiated from the gui, and is sent to the dispatcher, + which starts the process and returns the output when it is available. + If the script is interactive, it might also require input, which can be + passed via the dispatcher. + While reading output from the dispatcher the gui must remain responsive, + so that the view can be switched and the subprocess aborted, if desired. + To achieve this a separate thread is used for reading input from the + dispatcher, together with a mechanism for activating a handler in a + thread-safe way, 'ui.idle_add'. + """ + def __init__(self): + self.larch_dispatcher = None # dispatcher subprocess + self.progress_waiting = None # used by progress widget + + + def call(self, cmd, arg=[], atend=None): + self.cmd = cmd + self.arg = arg + # Callback on completion: + self.atend = atend # returns True for automatic return to normal view + if self.larch_dispatcher: + self.runcmd() + + else: + # Start a socket server to handle password requests + # Use a unix domain server in the abstract namespace + port = '\0larch-sudopw' + # Create the server + self.sserver = SocketServer.UnixStreamServer(port, MyHandler) + self.sst = threading.Thread(target=self.sserver.serve_forever, + args=()) + self.sst.start() + # Handle one request + #self.sserver.handle_request() + + # Start the larch dispatcher script + os.environ['SUDO_ASKPASS'] = base_dir + '/gui/askpass.py' + self._sudo_wait() + dispatcher = Popen(['sudo', '-A', '-k', + base_dir + '/gui/dispatcher.py'], + stdin=PIPE, + stdout=PIPE, + stderr=PIPE) + + # And a thread to read its output + self.istream = dispatcher.stdin + self.estream = dispatcher.stderr + self.t = threading.Thread(target=self.readinput, args=(dispatcher,)) + self.t.start() + + + def runcmd(self): + progress.start() # initialize progress widget + ui.runningtab(1) # switch view to progress widget + # Run command: + cx = '%s %s:%s\n' % (self.cmd, project_manager.project_dir, + serialize.dumps(self.arg)) + logger.addLine('++' + cx) + self.istream.write(cx) + self.istream.flush() + r = self.geterr() + if r: + ui.command('infoDialog', r, 'BUG') + + + def geterr(self): + r = "" + while True: + rx = self.estream.readline() + if rx: + rx = rx.strip() + else: + break + if rx == '!+': + break + if r: + r +='\n' + r += rx + return r + + + def readinput(self, dispatcher): + ostream = dispatcher.stdout + while True: + line = ostream.readline() + if not line: + break + id, line = line.rstrip().split(':', 1) + try: + if line[0] == '=': + self._stop_server() + # The dispatcher has just started, make it available + self.larch_dispatcher = dispatcher + # Reenable the gui, and queue the command + ui.idle_add(self._dispatcher_started, self.runcmd) + continue + elif line[0] != '/': + line = serialize.loads(line) + except: + line = '[[%s]]' % line + ui.idle_add(self.addline, line) # cross-thread call + + if self.larch_dispatcher == None: + self._stop_server() + ui.idle_add(self._dispatcher_started, None) + + + + def _sudo_wait(self): + ui.command(':larch.busy', [':larch'], True) + + + def _dispatcher_started(self, cmd): + if cmd: + cmd() + else: + ui.command('infoDialog', + ("%s:\n %s" % (ui.data('authfail'), self.geterr())), + 'sudo') + ui.command(':larch.busy', [':larch'], False) + + + def _stop_server(self): + # Stop the password socket-server + self.sserver.shutdown() + self.sserver = None + + + def close(self): + if self.larch_dispatcher: + self.istream.write('quit\n') + self.istream.flush() + self.larch_dispatcher.wait() + + + def interrupt(self): + logger.addLine('--Terminate--') + self.istream.write('kill\n') + self.istream.flush() + + + def addline(self, message): + """A line has been received from the script. + This must be handled in the main thread. + The input lines are filtered for pacman, mksquashfs and mkisofs + progress output so that appropriate progress reports can be given. + """ + if 'pacman:' in message: + progress.set(message[2:]) + if message.endswith('|100'): + progress.set() + message = message.rsplit('|', 1)[0].rstrip() + else: + return + + if 'mksquashfs:' in message: + progress.set(message[2:]) + self.progress_waiting = message + return + + if 'mkisofs:' in message: + progress.set(message[2:]) + self.progress_waiting = ">_mkisofs: completed" + return + + if self.progress_waiting: + progress.set() + progress.addLine(self.progress_waiting) + self.progress_waiting = None + + progress.addLine(message) + if message[0] == '/': + # process finished: + auto = False + if self.atend: + auto = self.atend(int(message[1:])) + progress.end(auto) + self.cmd = None + elif message.startswith('?>'): + # a query (yes/no) + # Pop up the query + reply = '??YES' if ui.command('confirmDialog', + message[2:], self.cmd) else '??NO' + self.istream.write(reply + '\n') + self.istream.flush() + + + def archin(self, cmd, installrepos): + args = ['-p', project_manager.profile_path, + '-i', project_manager.get_ipath()[1], + '-c', project_manager.get('pacman_cache')] + rf = project_manager.project_dir + '/pacman.conf.repos' + if installrepos and os.path.isfile(rf): + args += ['-r', rf] + self.call('archin', args + cmd.split()) + + + def larchify(self, oldsyssqf, oldlocales): + args = ['-p', project_manager.profile_path, + '-i', project_manager.get_ipath()[1],] + if oldsyssqf: + args.append('-o') + if oldlocales: + args.append('-l') + self.call('larchify', args) + + + def testmedium(self, path, atend): + self.call('live_iso', ['-T', '-S', path,], atend=atend) + + + def writemedium(self, path, args, dest=None): + if dest == 'BOOTISO': + args.append(path) + cmd = 'boot_iso' + else: + if path: + args += ['-S', path] + else: + args += ['-p', project_manager.profile_path] + args += ['-i', project_manager.get_ipath()[1]] + if dest != None: + args.append(dest) + cmd = 'live_part' + else: + cmd = 'live_iso' + self.call(cmd, args) + + + +class MyHandler(SocketServer.StreamRequestHandler): + def handle(self): + self._event = threading.Event() + # self.rfile is a file-like object created by the handler; + # we can now use e.g. readline() instead of raw recv() calls + data = self.rfile.readline().strip() + if data == 'pw-get': + ui.idle_add(self.dialog) + self._event.wait() + # Likewise, self.wfile is a file-like object used to write back + # to the client + self.wfile.write(self.pw) + + def dialog(self): + ok, self.pw = ui.command('textLineDialog', + ui.data('getpw'), + 'sudo', '', True) + if not ok: + self.pw = '' + self._event.set() + + + +fs = Fs() + +def filesystem(key, *args): + return exports[key](*args) + +__builtin__.filesystem = filesystem +__builtin__.larchscripts = LarchScripts() + +import project +project_manager.init() + |