Commits

Moises Henriquez committed 90434cc

Implemented multiple parts into the bot application.

Comments (0)

Files changed (6)

src/vpackager/bot.old.py

+#!/usr/bin/env python
+
+#    This file is part of vpackager.
+#
+#    vpackager is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License v2 as published by
+#    the Free Software Foundation.
+#
+#    vpackager 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 vpackager.  If not, see <http://www.gnu.org/licenses/>.
+
+import gtk
+import guitools
+import dbutils
+import buildutils
+import jobmanage
+import historymanager
+import threading
+import time
+import os
+import sqlite3
+import urllib2
+import urllib
+import shutil
+import glib
+
+__author__ = 'M0E-lnx'
+__author_email__ = 'moc.liamg@xnl.E0M'[::-1]
+__version__ = '0.1'
+
+class Dispatcher(threading.Thread):
+    """ Dispatcher thread used for dispatching jobs from the queue"""
+    def __init__(self, parent=None, dbase=None):
+        threading.Thread.__init__(self)
+        self.name = 'job-dispatcher'
+        self.parent = parent
+
+        if dbase:
+            self.dbase = dbase
+        else:
+            self.dbase = dbutils.dbase()
+
+    def _get_next_job(self):
+        ''' Get the next job in line from the database '''
+        print self.parent
+        return self.parent._get_next_job_()
+        #return self.dbase.GetNextJob()
+
+    def _log_job_start(self, _id):
+        ''' Log the job start to the database history'''
+        return self.dbase.LogJobStart(_id)
+
+    def _log_job_end(self, _id, stdoutpath, result):
+        ''' Log the job end to the database history'''
+        return self.dbase.LogJobEnd(_id, stdoutpath, result)
+
+    def _refresh_views(self):
+        ''' Refresh the screens that show the database status '''
+        self.parent.historyman._refresh_history_display()
+        self.parent.queueman._update_queue()
+
+    def _download_file(self, url):
+        '''download a remote file to the local directory'''
+        urllib.urlretrieve(url)
+
+    def run(self):
+        while self.parent._bot_run:
+            time.sleep(0.25)
+            njob = self._get_next_job()
+            if njob is None:
+                # NO new job in queue
+                self.parent._bot_run = False
+                print "Stopping bot because there is nothing to do"
+                return
+            # clear the textview
+            buf = self.parent.outputplayer.get_buffer()
+            buf.delete(buf.get_bounds()[0], buf.get_bounds()[1])
+            _id = njob[0]
+            info = self.dbase.GetJobDetails(_id)
+            pkg = info['app']
+            ver = info['ver']
+            src = info['srcURI']
+            desc = info['descURI']
+            rel = info['release']
+            _type = info['type']
+            patches = info['patches']
+            toinstall = info['install']
+            patchlist = []
+            if patches:
+                patchlist = patches.split(',')
+            # Log the build start time
+            self._log_job_start(_id)
+            # Update GUI displays for queue and history
+            self._refresh_views()
+            self.parent.job_indicator.set_markup('<b>%s-%s</b>'%(pkg,ver))
+            if src.startswith('/'):
+                srcob = buildutils.Source(src)
+            else:
+                srcob = buildutils.SourceURL(app = pkg, version=ver,
+                        srctype=_type, link=src)
+            sbpath = srcob.makeSlackBuild(buildno = rel)
+            # cd to the right place
+            os.chdir(os.path.dirname(sbpath))
+            if desc:
+                if desc.startswith('/'):
+                    shutil.copy2(desc, 'slack-desc')
+                else:
+                    self.parent._play_output('Downloading description from %s \n'% desc)
+                    u = urllib2.urlopen(desc)
+                    f = open('slack-desc', 'w')
+                    f.write(u.read())
+                    f.close()
+                    u.close()
+            if patchlist:
+                os.chdir(os.path.join(
+                    os.path.dirname(sbpath), 'patches'))
+                for patch in patchlist:
+                    if patch.startswith('/'):
+                        self.parent._play_output('\
+                                Adding patch %s to source tree \n'% patch)
+                        shutil.copy2(patch, os.path.join(
+                                    os.path.dirname(sbpath), 'patches',patch))
+                    else:
+                        self.parent._play_output('\
+                                Downloading patch %s \n'% patch)
+                        self._download_file(patch)
+            os.chdir(os.path.dirname(sbpath))
+            job = srcob.buildSlackBuild()
+            job.observer.add_callback(self.parent._play_output)
+            job.run()
+            while job._popen_running:
+                time.sleep(1)
+            retval = job.popen.returncode
+            if retval > 0:
+                result = 'Fail'
+            else:
+                result = 'Success'
+
+            # Save the output to a log file
+            buf = self.parent.outputplayer.get_buffer()
+            #logpath = sbpath.replace('.SlackBuild', '.build_log')
+            logpath = os.path.join(os.path.dirname(srcob.srclocation),
+                    '%s.build_log'% srcob.app)
+            output = buf.get_text(buf.get_bounds()[0], buf.get_bounds()[1])
+            f = open(logpath, 'w')
+            f.write(output)
+            f.close()
+            # Log the build result to the database
+            self._log_job_end(_id, logpath, result)
+            # Refresh the GUI display of the history and queue
+            self._refresh_views()
+            if result == "Success" and toinstall == "1":
+                # install the package
+                self.parent._play_output(
+                        '\nInstalling package \n')
+                try:
+                    proc = srcob.install_pkg()
+                    proc.observer.add_callback(self.parent._play_output)
+                    proc.run()
+                    while proc._popen_running:
+                        time.sleep(1)
+                    retval = proc.popen.returncode
+                except:
+                    print 'Error while installing package'
+                    pass
+        #self.dbase.close()
+
+class Tab(gtk.VBox):
+    ''' Generic tab for use in the tabstrip '''
+    def __init__(self, label='', stock=None, parent=None, dbase = None ):
+        gtk.VBox.__init__(self)
+        if dbase is None:
+            self.dbase = dbutils.dbase()
+        else:
+            self.dbase = dbase
+        ibox = gtk.VBox()
+        img = gtk.Image()
+        img.set_from_stock(stock, gtk.ICON_SIZE_BUTTON)
+        ibox.pack_start(img, False, False, 2)
+        lbl = gtk.Label()
+        lbl.set_use_markup(True)
+        lbl.set_markup(label)
+        ibox.pack_start(lbl, False, False, 2)
+        self.label = ibox
+        self.label.show_all()
+        self._parent = parent
+
+class TabStatus(Tab):
+    ''' Bot control and status tab '''
+    def __init__(self, *args, **kwargs):
+        Tab.__init__(self, label='Bot Status', 
+                stock=gtk.STOCK_PREFERENCES, **kwargs)
+        #Tab.__init__(self, label='Bot Status',
+        #        stock=gtk.STOCK_PREFERENCES)
+
+        topbox = gtk.HBox()
+        flt = gtk.Frame('Bot Status')
+        lvbox = gtk.VBox()
+        flt.add(lvbox)
+        ltbox = gtk.HBox()
+        lbbox = gtk.HBox()
+        lvbox.pack_start(ltbox, False , True, 2)
+        lvbox.pack_start(lbbox, False, True, 2)
+        lb0 = gtk.Label()
+        lb1 = gtk.Label()
+        lb0.set_use_markup(True)
+        lb1.set_use_markup(True)
+        lb0.set_markup('Current Status:')
+        lb1.set_markup('Current Job:')
+        self.label_bot_status = gtk.Label()
+        self.label_bot_status.set_use_markup(True)
+        self.label_current_job = gtk.Label()
+        self.label_current_job.set_use_markup(True)
+        ltbox.pack_start(lb0, False, False, 2)
+        ltbox.pack_start(self.label_bot_status, False, True, 2)
+        lbbox.pack_start(lb1, False, False, 2)
+        lbbox.pack_start(self.label_current_job, False, True, 2)
+        self._parent.bot_indicator = self.label_bot_status
+        self._parent.job_indicator = self.label_current_job
+
+        self._parent.bot_indicator.set_markup('<b>Stopped</b>')
+        self._parent.job_indicator.set_markup('<b>None</b>')
+
+        frt = gtk.Frame('Bot Control')
+        rvbox = gtk.VBox()
+        frt.add(rvbox)
+        rtbox = gtk.HBox()
+        rbbox = gtk.HBox()
+
+        btStopBot = guitools.vButton(label='Stop', stock=gtk.STOCK_NO)
+        btStartBot = guitools.vButton(label='Start', stock=gtk.STOCK_YES)
+        self.btStopBot = btStopBot
+        self.btStartBot = btStartBot
+        btStartBot.connect('clicked', self._start_bot)
+        btStopBot.connect('clicked', self._stop_bot)
+
+        rtbox.pack_start(btStartBot, False, False, 2)
+        rtbox.pack_start(btStopBot, False, False, 2)
+
+        rvbox.pack_start(rtbox, False, True, 2)
+        rvbox.pack_start(rbbox, False, True, 2)
+
+        topbox.pack_start(flt, True, True, 2)
+        topbox.pack_start(frt, False, False, 2)
+        self.pack_start(topbox, False, True, 4)
+
+        # add a textview inside a scrolled window for output monitoring.
+        self.scroll = gtk.ScrolledWindow()
+        self.scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        self.stdoutmon = gtk.TextView()
+        self.stdoutmon.set_property('editable', False)
+        for edge in (gtk.TEXT_WINDOW_LEFT, gtk.TEXT_WINDOW_RIGHT,
+                gtk.TEXT_WINDOW_TOP, gtk.TEXT_WINDOW_BOTTOM):
+            self.stdoutmon.set_border_window_size(edge, 4)
+        self.scroll.add(self.stdoutmon)
+
+        frmoutput = gtk.Frame('Build Output')
+        frmoutput.add(self.scroll)
+        self._parent.outputplayer = self.stdoutmon
+        self._parent.scrollarea = self.scroll
+
+        self.pack_start(frmoutput, True, True, 4)
+        self.mon_thread = threading.Thread(target=self.__bot_monitor,
+                name='vpackager-bot-monitor', args=())
+        self.mon_thread.start()
+        self.jobDispatcher = Dispatcher(parent = self._parent, 
+                dbase = self.dbase)
+
+    def __bot_monitor(self):
+        while self._parent._bot_run:
+            gtk.gdk.threads_enter()
+            self.btStartBot.set_property('sensitive', False)
+            self.btStopBot.set_property('sensitive', True)
+            self._parent.bot_indicator.set_markup('<b>Running</b>')
+            gtk.gdk.threads_leave()
+            time.sleep(0.25)
+        gtk.gdk.threads_enter()
+        self.btStartBot.set_property('sensitive', True)
+        self.btStopBot.set_property('sensitive', False)
+        self._parent.bot_indicator.set_markup('<b>Stopped</b>')
+        self._parent.job_indicator.set_markup('<b>None</b>')
+        gtk.gdk.threads_leave()
+
+    def __log_line(self, line):
+        buf = self.stdoutmon.get_buffer()
+        buf.insert(buf.get_end_iter(), line + '\n')
+
+    def _stop_bot(self, widget=None):
+        self._parent._bot_run = False
+        return
+
+    def _dispatch_job(self):
+        _id = 0
+        while self.dbase.GetQueue():
+            jobs = self.dbase.GetQueue()
+            job = jobs[_id]
+            yield job
+            _id += 1
+            jobs = self.dbase.GetNextJob()
+
+
+    def _start_bot(self, widget=None):
+        self._parent._bot_run = True
+        # ^^ Sends the signal to update the UI
+        if not self.mon_thread.isAlive():
+            del self.mon_thread
+            self.mon_thread = threading.Thread(target=self.__bot_monitor,
+                    name='vpackager-bot-monitor', args=())
+            self.mon_thread.start()
+        # Use a generator to dispatch jobs.
+        for job in self.dbase.GetQueue():
+            #print job[0]
+            self._do_build_job(job[0])
+
+    def _do_build_job(self, _id):
+        """ Perform the build job """
+        info = self.dbase.GetJobDetails(_id)
+        pkg = info['app']
+        ver = info['ver']
+        src = info['srcURI']
+        desc = info['descURI']
+        rel = info['release']
+        _type = info['type']
+        toinstall = info['install']
+        patches = info['patches']
+        patchlist = []
+        if patches:
+            patchlist = patches.split(',')
+
+        # Log the timestamp at the job start.
+        self.dbase.LogJobStart(_id)
+        # update the views that display the queue and history
+        self._parent.historyman._refresh_history_display()
+        self._parent.queueman._update_queue()
+        # clear the text view
+        buf = self._parent.outputplayer.get_buffer()
+        start, end = buf.get_bounds()
+        buf.delete(start, end)
+        # create the source object to begin work
+        if src.startswith('/'):
+            srcob = buildutils.Source(src)
+        else:
+            srcob = buildutils.SourceURL(app = pkg, 
+                    version = ver, srctype = _type,
+                    link = src)
+        # get the path to the slackbuild
+        sbpath = srcob.makeSlackBuild(buildno = rel)
+        # cd to the right place
+        os.chdir(os.path.dirname(sbpath))
+
+        # Create the job
+        job_ = srcob.buildSlackBuild()
+        job_.observer.add_callback(self._parent._play_output)
+        # hand the build process off to another thread
+        t = threading.Thread(target=job_.run, args=())
+        t.start()
+        time.sleep(1)
+        #job_.run()
+        while job_.popen.running:
+            time.sleep(1)
+        retval = job_.popen.returncode
+        if retval > 0:
+            result = 'Fail'
+        else:
+            result = 'Success'
+
+        # Save the output to a log file
+        start, end = buf.get_bounds()
+        logpath = os.path.join(os.path.dirname(srcob.srclocation),
+                '%s.build_log'% srcob.app)
+        output = buf.get_text(start, end)
+        f = open(logpath, 'w')
+        f.write(output)
+        f.close()
+
+        # Log the job end timestamp
+        self.dbase.LogJobEnd(_id, logpath, result)
+
+        # Update the view
+        self._parent.queueman._update_queue()
+        self._parent.historyman._refresh_history_display()
+
+
+
+        #if not self.jobDispatcher.isAlive():
+        #    del self.jobDispatcher
+        #    self.jobDispatcher = Dispatcher(parent=self._parent,
+        #            dbase=self.dbase)
+        #    self.jobDispatcher.start()
+        #return
+
+class TabQueue(Tab):
+    """ Job queue tab. Used to manage build jobs."""
+    def __init__(self, dbase=None, *args, **kwargs):
+        Tab.__init__(self, label = 'Job Queue',
+                stock = gtk.STOCK_INDEX, **kwargs)
+        if not self.dbase:
+            if dbase is None:
+                self.dbase = dbutils.dbase()
+            else:
+                self.dbase = dbase
+        cont = jobmanage.Main()
+        self.pack_start(cont, True, True, 2)
+        self._parent.queueman = cont
+
+    def _refresh_ui(self):
+        return self._parent.queueman._update_queue()
+
+class TabHistory(Tab):
+    """ Tab displaying the job build history"""
+    def __init__(self, dbase = None, *args, **kwargs):
+        Tab.__init__(self, label = 'Job History',
+                stock=gtk.STOCK_JUSTIFY_FILL, **kwargs)
+        if not self.dbase:
+            if not dbase:
+                self.dbase = dbutils.dbase()
+            else:
+                self.dbase = dbase
+        cont = historymanager.Main()
+        self.pack_start(cont, True, True, 2)
+        self._parent.historyman = cont
+
+    def _refresh_ui(self):
+        return self._parent.historyman._refresh_history_display()
+
+class TabStrip(gtk.Notebook):
+    """ Tabstrip used to display the main content of the application"""
+    def __init__(self):
+        gtk.Notebook.__init__(self)
+        self.set_property('tab-pos', gtk.POS_LEFT)
+        self.connect('switch-page', self._switch_page_event)
+
+    def _switch_page_event(self, widget=None, page=None, page_num=0):
+        if page_num in (1,2):
+            return self.get_nth_page(page_num)._refresh_ui()
+        #print page_num
+        #return
+
+        child = self.get_nth_page(page_num)
+        #return child.
+
+class App(gtk.Window):
+    """ Main app GUI """
+    def __init__(self):
+        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
+        # Load the database
+        self.dbase = dbutils.dbase()
+        self.set_title('vpackager')
+        self.connect('destroy', self._exit)
+        datadir = os.path.dirname(os.path.abspath(__file__))
+        datadir = os.path.join(datadir, 'data')
+        self.set_icon_from_file(os.path.join(datadir, 'vpackager.png'))
+        self.set_size_request(950, 500)
+        self.body = gtk.VBox()
+        # make sure the db is ok
+        self._check_db_sanity()
+        self.outputplayer = None
+        self.historyman = None
+        self.queueman = None
+        self._bot_run = False
+        self.body.pack_start(self._get_menubar(), False, True, 4)
+        self.body.pack_start(self._get_notebook(), True, True, 4)
+
+        self.add(self.body)
+
+    def _get_next_job_(self):
+        return self.dbase.GetNextJob()
+
+    def _get_menubar(self):
+        mb = gtk.MenuBar()
+        filemenu = gtk.Menu()
+        filem = gtk.MenuItem("File")
+        filem.set_submenu(filemenu)
+
+        exit = gtk.MenuItem("Exit")
+        exit.connect("activate", self._exit)
+        filemenu.append(exit)
+
+        mb.append(filem)
+
+        helpm = gtk.MenuItem("Help")
+        helpmenu = gtk.Menu()
+        helpm.set_submenu(helpmenu)
+
+        about = gtk.MenuItem("About")
+        about.connect("activate", self._show_about)
+        helpmenu.append(about)
+        mb.append(helpm)
+        return mb
+
+    def _show_about(self, widget=None):
+        """Display the about window"""
+        dia = guitools.About()
+        if dia.run():
+            dia.destroy()
+
+
+    def _play_output(self, line=None):
+        gtk.gdk.threads_enter()
+        buf = self.outputplayer.get_buffer()
+        buf.insert(buf.get_end_iter(), line)
+        #gtk.gdk.threads_enter()
+        vad = self.scrollarea.get_vadjustment()
+        newpos = vad.get_upper() - vad.get_page_size()
+        vad.set_value(newpos)
+        self.scrollarea.set_vadjustment(vad)
+        gtk.gdk.threads_leave()
+
+
+    def _get_notebook(self):
+        """ Main content notebook"""
+        strip = TabStrip()
+        stats = TabStatus(parent=self, dbase = self.dbase)
+        strip.append_page(stats, stats.label)
+
+        queuet = TabQueue(parent=self, dbase = self.dbase)
+        strip.append_page(queuet, queuet.label)
+
+        history = TabHistory(parent=self, dbase = self.dbase)
+        strip.append_page(history, history.label)
+
+
+        return strip
+
+    def _get_bottombar(self):
+        return gtk.Label('Bottom bar')
+
+    def _exit(self, widget=None):
+        self._stop_bot = True
+        time.sleep(0.25)
+        self.dbase.close()
+        return gtk.main_quit()
+
+    def _check_db_sanity(self):
+        if self.dbase.verify_tables():
+            self.dbase.CreateTables()
+
+if __name__ == '__main__':
+    gtk.gdk.threads_init()
+    w = App()
+    w.show_all()
+    gtk.gdk.threads_enter()
+    gtk.main()
+

src/vpackager/bot.py

 #    along with vpackager.  If not, see <http://www.gnu.org/licenses/>.
 
 import gtk
+import dbutils
+import dbviews
 import guitools
-import dbutils
+import viewcontrollers
+import threading
+import Queue
 import buildutils
-import jobmanage
-import historymanager
-import threading
+import os
+import shutil
+import urllib
 import time
-import os
-import sqlite3
-import urllib2
-import urllib
-import shutil
-import glib
 
 __author__ = 'M0E-lnx'
 __author_email__ = 'moc.liamg@xnl.E0M'[::-1]
 __version__ = '0.1'
 
+
 class Dispatcher(threading.Thread):
-    """ Dispatcher thread used for dispatching jobs from the queue"""
-    def __init__(self, parent=None, dbase=None):
+    """ Threaded Build process"""
+    def __init__(self, model, view):
         threading.Thread.__init__(self)
-        self.name = 'job-dispatcher'
-        self.parent = parent
+        self.view = view
+        self.model = model
 
-        if dbase:
-            self.dbase = dbase
+    def get_builder(self, srcuri, _id):
+        """ Returns the correct source object based on the source URI"""
+        if srcuri.startswith('/'):
+            srcob = buildutils.Source(srcuri)
         else:
-            self.dbase = dbutils.dbase()
+            # we need to get the details about this job
+            details = self.model.GetJobDetails(_id)
+            srcob = buildutils.SourceURL(
+                app = details['app'], version = details['ver'],
+                srctype = details['type'], link=srcuri)
+        return srcob
 
-    def _get_next_job(self):
-        ''' Get the next job in line from the database '''
-        print self.parent
-        return self.parent._get_next_job_()
-        #return self.dbase.GetNextJob()
+    def process_source(self, srcuri, workdir):
+        """ Copy the source to the right location """
+        if srcuri.startswith('/'):
+            return shutil.copy2(srcuri, workdir)
+        return
 
-    def _log_job_start(self, _id):
-        ''' Log the job start to the database history'''
-        return self.dbase.LogJobStart(_id)
+    def process_description(self, descuri, workdir):
+        """ Copy or download the package description to the right place"""
+        if not descuri:
+            return
+        if descuri is None:
+            return
+        if descuri.startswith('/'):
+            return shutil.copy2(descuri, workdir)
+        else:
+            _prevdir = os.getcwd()
+            os.chdir(workdir)
+            urllib.urlretrieve(descuri, 'slack-desc')
+            return os.chdir(_prevdir)
+        return
 
-    def _log_job_end(self, _id, stdoutpath, result):
-        ''' Log the job end to the database history'''
-        return self.dbase.LogJobEnd(_id, stdoutpath, result)
+    def process_patches(self, patchlist, workdir):
+        """ copy or download any patches into the source tree"""
+        if not patchlist:
+            return
+        if patchlist is None:
+            return
+        if patchlist is "None":
+            return
+        _prevdir = os.getcwd()
+        patchesdir = os.path.join(workdir, 'patches')
+        for patch in patchlist.split(','):
+            if patch.startswith('/'):
+                shutil.copy2(patch, patchesdir)
+            else:
+                os.chdir(patchesdir)
+                urllib.urlretrieve(patch)
+        return os.chdir(_prevdir)
 
-    def _refresh_views(self):
-        ''' Refresh the screens that show the database status '''
-        self.parent.historyman._refresh_history_display()
-        self.parent.queueman._update_queue()
+    def clear_textview(self):
+        buf = self.view.textview.get_buffer()
+        start, end = buf.get_bounds()
+        return buf.delete(start, end)
 
-    def _download_file(self, url):
-        '''download a remote file to the local directory'''
-        urllib.urlretrieve(url)
+    def log_output_to_file(self, logfile_abspath):
+        buf = self.view.textview.get_buffer()
+        start, end = buf.get_bounds()
+        f = open(logfile_abspath, 'w')
+        f.write(buf.get_text(start, end))
+        f.close()
+        return
+
+    def print_to_log(self, data):
+        return self.view.PlayOutput(data)
 
     def run(self):
-        while self.parent._bot_run:
-            time.sleep(0.25)
-            njob = self._get_next_job()
-            if njob is None:
-                # NO new job in queue
-                self.parent._bot_run = False
-                print "Stopping bot because there is nothing to do"
-                return
-            # clear the textview
-            buf = self.parent.outputplayer.get_buffer()
-            buf.delete(buf.get_bounds()[0], buf.get_bounds()[1])
-            _id = njob[0]
-            info = self.dbase.GetJobDetails(_id)
-            pkg = info['app']
-            ver = info['ver']
-            src = info['srcURI']
-            desc = info['descURI']
-            rel = info['release']
-            _type = info['type']
-            patches = info['patches']
-            toinstall = info['install']
-            patchlist = []
-            if patches:
-                patchlist = patches.split(',')
-            # Log the build start time
-            self._log_job_start(_id)
-            # Update GUI displays for queue and history
-            self._refresh_views()
-            self.parent.job_indicator.set_markup('<b>%s-%s</b>'%(pkg,ver))
-            if src.startswith('/'):
-                srcob = buildutils.Source(src)
-            else:
-                srcob = buildutils.SourceURL(app = pkg, version=ver,
-                        srctype=_type, link=src)
-            sbpath = srcob.makeSlackBuild(buildno = rel)
-            # cd to the right place
-            os.chdir(os.path.dirname(sbpath))
-            if desc:
-                if desc.startswith('/'):
-                    shutil.copy2(desc, 'slack-desc')
-                else:
-                    self.parent._play_output('Downloading description from %s \n'% desc)
-                    u = urllib2.urlopen(desc)
-                    f = open('slack-desc', 'w')
-                    f.write(u.read())
-                    f.close()
-                    u.close()
-            if patchlist:
-                os.chdir(os.path.join(
-                    os.path.dirname(sbpath), 'patches'))
-                for patch in patchlist:
-                    if patch.startswith('/'):
-                        self.parent._play_output('\
-                                Adding patch %s to source tree \n'% patch)
-                        shutil.copy2(patch, os.path.join(
-                                    os.path.dirname(sbpath), 'patches',patch))
-                    else:
-                        self.parent._play_output('\
-                                Downloading patch %s \n'% patch)
-                        self._download_file(patch)
-            os.chdir(os.path.dirname(sbpath))
-            job = srcob.buildSlackBuild()
-            job.observer.add_callback(self.parent._play_output)
-            job.run()
-            while job._popen_running:
-                time.sleep(1)
-            retval = job.popen.returncode
-            if retval > 0:
-                result = 'Fail'
-            else:
-                result = 'Success'
+        while self.view.RUN_BOT:
+            time.sleep(1)
+            job_id = self.model.GetNextJob()
+            job_info = self.model.GetJobDetails(job_id)
 
-            # Save the output to a log file
-            buf = self.parent.outputplayer.get_buffer()
-            #logpath = sbpath.replace('.SlackBuild', '.build_log')
-            logpath = os.path.join(os.path.dirname(srcob.srclocation),
-                    '%s.build_log'% srcob.app)
-            output = buf.get_text(buf.get_bounds()[0], buf.get_bounds()[1])
-            f = open(logpath, 'w')
-            f.write(output)
-            f.close()
-            # Log the build result to the database
-            self._log_job_end(_id, logpath, result)
-            # Refresh the GUI display of the history and queue
-            self._refresh_views()
-            if result == "Success" and toinstall == "1":
-                # install the package
-                self.parent._play_output(
-                        '\nInstalling package \n')
-                try:
-                    proc = srcob.install_pkg()
-                    proc.observer.add_callback(self.parent._play_output)
-                    proc.run()
-                    while proc._popen_running:
-                        time.sleep(1)
-                    retval = proc.popen.returncode
-                except:
-                    print 'Error while installing package'
-                    pass
-        #self.dbase.close()
+        # get the job id from the queue
+        _id = self.model.GetNextJob()
+        if _id is None:
+            self.print_to_log(
+                "\n   *** No more jobs in queue. Stopping bot ***")
+            return self.view.StopBot()
 
-class Tab(gtk.VBox):
-    ''' Generic tab for use in the tabstrip '''
-    def __init__(self, label='', stock=None, parent=None, dbase = None ):
-        gtk.VBox.__init__(self)
-        if dbase is None:
-            self.dbase = dbutils.dbase()
-        else:
-            self.dbase = dbase
-        ibox = gtk.VBox()
-        img = gtk.Image()
-        img.set_from_stock(stock, gtk.ICON_SIZE_BUTTON)
-        ibox.pack_start(img, False, False, 2)
-        lbl = gtk.Label()
-        lbl.set_use_markup(True)
-        lbl.set_markup(label)
-        ibox.pack_start(lbl, False, False, 2)
-        self.label = ibox
-        self.label.show_all()
-        self._parent = parent
+        # Clear the textview for the previous build log
+        self.clear_textview()
+        # Get the vpackager job
+        details = self.model.GetJobDetails(_id)
+        # Show the bot status in the view GUI...
+        self.view.IndicateBotRunning(app = details['app'],
+                                     ver = details['ver'])
+        # begin the build job
+        self.model.LogJobStart(_id)
+        # Do something to update the bot status UI notifications
+        # FIXME: ^^
 
-class TabStatus(Tab):
-    ''' Bot control and status tab '''
-    def __init__(self, *args, **kwargs):
-        Tab.__init__(self, label='Bot Status', 
-                stock=gtk.STOCK_PREFERENCES, **kwargs)
-        #Tab.__init__(self, label='Bot Status',
-        #        stock=gtk.STOCK_PREFERENCES)
-
-        topbox = gtk.HBox()
-        flt = gtk.Frame('Bot Status')
-        lvbox = gtk.VBox()
-        flt.add(lvbox)
-        ltbox = gtk.HBox()
-        lbbox = gtk.HBox()
-        lvbox.pack_start(ltbox, False , True, 2)
-        lvbox.pack_start(lbbox, False, True, 2)
-        lb0 = gtk.Label()
-        lb1 = gtk.Label()
-        lb0.set_use_markup(True)
-        lb1.set_use_markup(True)
-        lb0.set_markup('Current Status:')
-        lb1.set_markup('Current Job:')
-        self.label_bot_status = gtk.Label()
-        self.label_bot_status.set_use_markup(True)
-        self.label_current_job = gtk.Label()
-        self.label_current_job.set_use_markup(True)
-        ltbox.pack_start(lb0, False, False, 2)
-        ltbox.pack_start(self.label_bot_status, False, True, 2)
-        lbbox.pack_start(lb1, False, False, 2)
-        lbbox.pack_start(self.label_current_job, False, True, 2)
-        self._parent.bot_indicator = self.label_bot_status
-        self._parent.job_indicator = self.label_current_job
-
-        self._parent.bot_indicator.set_markup('<b>Stopped</b>')
-        self._parent.job_indicator.set_markup('<b>None</b>')
-
-        frt = gtk.Frame('Bot Control')
-        rvbox = gtk.VBox()
-        frt.add(rvbox)
-        rtbox = gtk.HBox()
-        rbbox = gtk.HBox()
-
-        btStopBot = guitools.vButton(label='Stop', stock=gtk.STOCK_NO)
-        btStartBot = guitools.vButton(label='Start', stock=gtk.STOCK_YES)
-        self.btStopBot = btStopBot
-        self.btStartBot = btStartBot
-        btStartBot.connect('clicked', self._start_bot)
-        btStopBot.connect('clicked', self._stop_bot)
-
-        rtbox.pack_start(btStartBot, False, False, 2)
-        rtbox.pack_start(btStopBot, False, False, 2)
-
-        rvbox.pack_start(rtbox, False, True, 2)
-        rvbox.pack_start(rbbox, False, True, 2)
-
-        topbox.pack_start(flt, True, True, 2)
-        topbox.pack_start(frt, False, False, 2)
-        self.pack_start(topbox, False, True, 4)
-
-        # add a textview inside a scrolled window for output monitoring.
-        self.scroll = gtk.ScrolledWindow()
-        self.scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-        self.stdoutmon = gtk.TextView()
-        self.stdoutmon.set_property('editable', False)
-        for edge in (gtk.TEXT_WINDOW_LEFT, gtk.TEXT_WINDOW_RIGHT,
-                gtk.TEXT_WINDOW_TOP, gtk.TEXT_WINDOW_BOTTOM):
-            self.stdoutmon.set_border_window_size(edge, 4)
-        self.scroll.add(self.stdoutmon)
-
-        frmoutput = gtk.Frame('Build Output')
-        frmoutput.add(self.scroll)
-        self._parent.outputplayer = self.stdoutmon
-        self._parent.scrollarea = self.scroll
-
-        self.pack_start(frmoutput, True, True, 4)
-        self.mon_thread = threading.Thread(target=self.__bot_monitor,
-                name='vpackager-bot-monitor', args=())
-        self.mon_thread.start()
-        self.jobDispatcher = Dispatcher(parent = self._parent, 
-                dbase = self.dbase)
-
-    def __bot_monitor(self):
-        while self._parent._bot_run:
-            gtk.gdk.threads_enter()
-            self.btStartBot.set_property('sensitive', False)
-            self.btStopBot.set_property('sensitive', True)
-            self._parent.bot_indicator.set_markup('<b>Running</b>')
-            gtk.gdk.threads_leave()
-            time.sleep(0.25)
-        gtk.gdk.threads_enter()
-        self.btStartBot.set_property('sensitive', True)
-        self.btStopBot.set_property('sensitive', False)
-        self._parent.bot_indicator.set_markup('<b>Stopped</b>')
-        self._parent.job_indicator.set_markup('<b>None</b>')
-        gtk.gdk.threads_leave()
-
-    def __log_line(self, line):
-        buf = self.stdoutmon.get_buffer()
-        buf.insert(buf.get_end_iter(), line + '\n')
-
-    def _stop_bot(self, widget=None):
-        self._parent._bot_run = False
-        return
-
-    def _dispatch_job(self):
-        _id = 0
-        while self.dbase.GetQueue():
-            jobs = self.dbase.GetQueue()
-            job = jobs[_id]
-            yield job
-            _id += 1
-            jobs = self.dbase.GetNextJob()
-
-
-    def _start_bot(self, widget=None):
-        self._parent._bot_run = True
-        # ^^ Sends the signal to update the UI
-        if not self.mon_thread.isAlive():
-            del self.mon_thread
-            self.mon_thread = threading.Thread(target=self.__bot_monitor,
-                    name='vpackager-bot-monitor', args=())
-            self.mon_thread.start()
-        # Use a generator to dispatch jobs.
-        for job in self.dbase.GetQueue():
-            #print job[0]
-            self._do_build_job(job[0])
-
-    def _do_build_job(self, _id):
-        """ Perform the build job """
-        info = self.dbase.GetJobDetails(_id)
-        pkg = info['app']
-        ver = info['ver']
-        src = info['srcURI']
-        desc = info['descURI']
-        rel = info['release']
-        _type = info['type']
-        toinstall = info['install']
-        patches = info['patches']
-        patchlist = []
-        if patches:
-            patchlist = patches.split(',')
-
-        # Log the timestamp at the job start.
-        self.dbase.LogJobStart(_id)
-        # update the views that display the queue and history
-        self._parent.historyman._refresh_history_display()
-        self._parent.queueman._update_queue()
-        # clear the text view
-        buf = self._parent.outputplayer.get_buffer()
-        start, end = buf.get_bounds()
-        buf.delete(start, end)
-        # create the source object to begin work
-        if src.startswith('/'):
-            srcob = buildutils.Source(src)
-        else:
-            srcob = buildutils.SourceURL(app = pkg, 
-                    version = ver, srctype = _type,
-                    link = src)
-        # get the path to the slackbuild
-        sbpath = srcob.makeSlackBuild(buildno = rel)
-        # cd to the right place
+        srcob = self.get_builder()
+        # create the build script
+        sbpath = srcob.makeSlackBuild(buildno = details['release'])
+        # cd to the right place to perform operations
         os.chdir(os.path.dirname(sbpath))
-
-        # Create the job
-        job_ = srcob.buildSlackBuild()
-        job_.observer.add_callback(self._parent._play_output)
-        # hand the build process off to another thread
-        t = threading.Thread(target=job_.run, args=())
-        t.start()
-        time.sleep(1)
-        #job_.run()
-        while job_.popen.running:
+        # Process the source
+        self.process_source(details['srcURI'], sbpath)
+        # process the description
+        self.process_description(details['descuri'], sbpath)
+        # Process patches
+        self.process_patches(details['patches'], sbpath)
+        # change back to the original work path
+        os.chdir(os.path.dirname(sbpath))
+        job = srcob.buildSlackBuild()
+        # Add a callback to play the output to the GUI
+        job.observer.add_callback(self.view.PlayOutput)
+        job.run()
+        while job._popen_running:
             time.sleep(1)
-        retval = job_.popen.returncode
+        retval = job.popen.returncode
         if retval > 0:
             result = 'Fail'
         else:
             result = 'Success'
 
-        # Save the output to a log file
-        start, end = buf.get_bounds()
+        # Log the output to a file
         logpath = os.path.join(os.path.dirname(srcob.srclocation),
-                '%s.build_log'% srcob.app)
-        output = buf.get_text(start, end)
-        f = open(logpath, 'w')
-        f.write(output)
-        f.close()
+                               '%s.build_log'% details['app'])
+        # Log the job end to the database
+        self.log_output_to_file(logpath)
+        self.model.LogJobEnd(_id, logpath, result)
+        if result == 'Success' and details['install'] == "1":
+            # install the package
+            self.print_to_log("\n + Installing package per user request")
+            try:
+                proc = srcob.install_pkg()
+                # again, pass the observer to se can see the output
+                proc.observer.add_callback(self.view.PlayOutput)
+                proc.run()
+                while proc._popen_running:
+                    time.sleep(1)
+                retval = proc.popen.returncode
+            except:
+                pass
 
-        # Log the job end timestamp
-        self.dbase.LogJobEnd(_id, logpath, result)
+class StatusTab(gtk.VBox):
+    """ Content of the status tab packed in a vbox that can be
+    appended as a page to the tabstrip"""
 
-        # Update the view
-        self._parent.queueman._update_queue()
-        self._parent.historyman._refresh_history_display()
+    def __init__(self, model):
+        gtk.VBox.__init__(self)
+        self.model = model
+        self.bot_state_indicators = {}
+        self.bot_control_buttons = {}
+        self.bot_state_indicators['state'] = gtk.Label()
+        self.bot_state_indicators['running_job'] = gtk.Label()
+        for label in self.bot_state_indicators.values():
+            label.set_property('xalign', 0.0)
+        self.bot_control_buttons['start'] = guitools.vButton(
+            stock=gtk.STOCK_YES, label='Start')
+        self.bot_control_buttons['stop'] = guitools.vButton(
+            stock=gtk.STOCK_NO, label='Stop')
+        for lb  in self.bot_state_indicators.values():
+            lb.set_use_markup(True)
+        self.pack_start(self._get_top_frames(), False, True, 4)
+        self.pack_start(self._get_output_display(), True, True, 4)
+        self.RUN_BOT = False
+        self.bot_control_buttons['start'].connect(
+            'clicked', self.StartBot)
+        self.bot_control_buttons['stop'].connect(
+            'clicked', self.StopBot)
+        self.StopBot(self.bot_control_buttons['stop'])
+        self.dispatcher = Dispatcher(model = self.model, view = self)
 
+    def IndicateBotRunning(self, app, ver):
+        self.bot_state_indicators['state'].set_label(
+            'Running')
+        self.bot_state_indicators['running_job'].set_label(
+            '%s-%s'% (app, ver))
 
+    def IndicateBotStopped(self):
+        self.bot_state_indicators['state'].set_label('Idle')
+        self.bot_state_indicators['running_job'].set_label('None')
+        self.bot_control_buttons['stop'].set_sensitive(False)
+        self.bot_control_buttons['start'].set_sensitive(True)
 
-        #if not self.jobDispatcher.isAlive():
-        #    del self.jobDispatcher
-        #    self.jobDispatcher = Dispatcher(parent=self._parent,
-        #            dbase=self.dbase)
-        #    self.jobDispatcher.start()
-        #return
+    def StopBot(self, widget=None):
+        self.RUN_BOT = False
+        return self.IndicateBotStopped()
 
-class TabQueue(Tab):
-    """ Job queue tab. Used to manage build jobs."""
-    def __init__(self, dbase=None, *args, **kwargs):
-        Tab.__init__(self, label = 'Job Queue',
-                stock = gtk.STOCK_INDEX, **kwargs)
-        if not self.dbase:
-            if dbase is None:
-                self.dbase = dbutils.dbase()
-            else:
-                self.dbase = dbase
-        cont = jobmanage.Main()
-        self.pack_start(cont, True, True, 2)
-        self._parent.queueman = cont
+    def StartBot(self, widget=None):
+        self.RUN_BOT = True
+        self.bot_state_indicators['state'].set_label(
+            '<b>Running</b>')
+        if widget:
+            widget.set_sensitive(False)
+            self.bot_control_buttons['stop'].set_sensitive(True)
+        try:
+            self.dispatcher.start()
+        except:
+            del self.dispatcher
+            self.dispatcher = Dispatcher(model = self.mode, view = self)
+            self.dispatcher.start()
+        return
 
-    def _refresh_ui(self):
-        return self._parent.queueman._update_queue()
+    def _get_top_frames(self):
+        """ Top frames that show the bot status and bot control buttons."""
+        section = gtk.HBox()
+        frm_tl = gtk.Frame('Bot Status')
+        frm_tr = gtk.Frame('Build Bot Control')
+        frm_tl_vbox = gtk.VBox()
+        frm_tl.add(frm_tl_vbox)
+        line1 = gtk.HBox()
+        line2 = gtk.HBox()
+        frm_tl_vbox.pack_start(line1, False, True, 2)
+        frm_tl_vbox.pack_start(line2, False, True, 2)
+        lb_bot_state = gtk.Label('Current State:')
+        lb_running_job = gtk.Label('Current Job:')
+        line1.pack_start(lb_bot_state, False, False, 2)
+        line2.pack_start(lb_running_job, False, False, 2)
+        line1.pack_start(
+            self.bot_state_indicators['state'], True, True, 2)
+        line2.pack_start(
+            self.bot_state_indicators['running_job'], True, True, 2)
 
-class TabHistory(Tab):
-    """ Tab displaying the job build history"""
-    def __init__(self, dbase = None, *args, **kwargs):
-        Tab.__init__(self, label = 'Job History',
-                stock=gtk.STOCK_JUSTIFY_FILL, **kwargs)
-        if not self.dbase:
-            if not dbase:
-                self.dbase = dbutils.dbase()
-            else:
-                self.dbase = dbase
-        cont = historymanager.Main()
-        self.pack_start(cont, True, True, 2)
-        self._parent.historyman = cont
+        # Pack the bot control widgets
+        rvbox = gtk.VBox()
+        rhbox = gtk.HBox()
+        rvbox.pack_start(rhbox, True, True, 2)
+        rhbox.pack_start(
+            self.bot_control_buttons['start'], False, False, 2)
+        rhbox.pack_start(
+            self.bot_control_buttons['stop'], False, False, 2)
+        frm_tr.add(rvbox)
 
-    def _refresh_ui(self):
-        return self._parent.historyman._refresh_history_display()
+        section.pack_start(frm_tl, True, True, 2)
+        section.pack_start(frm_tr, False, False, 2)
 
-class TabStrip(gtk.Notebook):
-    """ Tabstrip used to display the main content of the application"""
-    def __init__(self):
-        gtk.Notebook.__init__(self)
-        self.set_property('tab-pos', gtk.POS_LEFT)
-        self.connect('switch-page', self._switch_page_event)
+        return section
 
-    def _switch_page_event(self, widget=None, page=None, page_num=0):
-        if page_num in (1,2):
-            return self.get_nth_page(page_num)._refresh_ui()
-        #print page_num
-        #return
-
-        child = self.get_nth_page(page_num)
-        #return child.
-
-class App(gtk.Window):
-    """ Main app GUI """
-    def __init__(self):
-        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
-        # Load the database
-        self.dbase = dbutils.dbase()
-        self.set_title('vpackager')
-        self.connect('destroy', self._exit)
-        datadir = os.path.dirname(os.path.abspath(__file__))
-        datadir = os.path.join(datadir, 'data')
-        self.set_icon_from_file(os.path.join(datadir, 'vpackager.png'))
-        self.set_size_request(950, 500)
-        self.body = gtk.VBox()
-        # make sure the db is ok
-        self._check_db_sanity()
-        self.outputplayer = None
-        self.historyman = None
-        self.queueman = None
-        self._bot_run = False
-        self.body.pack_start(self._get_menubar(), False, True, 4)
-        self.body.pack_start(self._get_notebook(), True, True, 4)
-
-        self.add(self.body)
-
-    def _get_next_job_(self):
-        return self.dbase.GetNextJob()
-
-    def _get_menubar(self):
-        mb = gtk.MenuBar()
-        filemenu = gtk.Menu()
-        filem = gtk.MenuItem("File")
-        filem.set_submenu(filemenu)
-
-        exit = gtk.MenuItem("Exit")
-        exit.connect("activate", self._exit)
-        filemenu.append(exit)
-
-        mb.append(filem)
-
-        helpm = gtk.MenuItem("Help")
-        helpmenu = gtk.Menu()
-        helpm.set_submenu(helpmenu)
-
-        about = gtk.MenuItem("About")
-        about.connect("activate", self._show_about)
-        helpmenu.append(about)
-        mb.append(helpm)
-        return mb
-
-    def _show_about(self, widget=None):
-        """Display the about window"""
-        dia = guitools.About()
-        if dia.run():
-            dia.destroy()
-
-
-    def _play_output(self, line=None):
+    def PlayOutput(self, data):
         gtk.gdk.threads_enter()
-        buf = self.outputplayer.get_buffer()
-        buf.insert(buf.get_end_iter(), line)
-        #gtk.gdk.threads_enter()
-        vad = self.scrollarea.get_vadjustment()
+        buf = self.textview.get_buffer()
+        buf.insert(buf.get_end_iter(), data)
+        # scroll the lines if necessary
+        vad = self.scrl.get_vadjustment()
         newpos = vad.get_upper() - vad.get_page_size()
         vad.set_value(newpos)
-        self.scrollarea.set_vadjustment(vad)
+        self.scrl.set_vadjustment(vad)
         gtk.gdk.threads_leave()
 
+    def _get_output_display(self):
+        self.scrl = gtk.ScrolledWindow()
+        self.scrl.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        self.textview = gtk.TextView()
+        for edge in (gtk.TEXT_WINDOW_LEFT, gtk.TEXT_WINDOW_RIGHT,
+                     gtk.TEXT_WINDOW_TOP, gtk.TEXT_WINDOW_BOTTOM):
+            self.textview.set_border_window_size(edge, 4)
+        self.scrl.add(self.textview)
 
-    def _get_notebook(self):
-        """ Main content notebook"""
-        strip = TabStrip()
-        stats = TabStatus(parent=self, dbase = self.dbase)
-        strip.append_page(stats, stats.label)
+        return self.scrl
 
-        queuet = TabQueue(parent=self, dbase = self.dbase)
-        strip.append_page(queuet, queuet.label)
 
-        history = TabHistory(parent=self, dbase = self.dbase)
-        strip.append_page(history, history.label)
+class Gui(gtk.Window):
+    """ Main window for vpackager"""
+    def __init__(self, dbase_model):
+        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
+        self.dbase_model = dbase_model
+        self.body = gtk.VBox()
+        self.set_title('vpackager')
+        self.set_position(gtk.WIN_POS_CENTER)
+        self.body.pack_start(gtk.Label('Menu goes here'), False, True, 2)
+        self.tabstrip = guitools.TabStrip()
+        self.bot_tab = StatusTab(model = self.dbase_model)
+        #self.bot_tab = StatusTab()
+        self.queue_view = dbviews.QueueView()
+        self.history_view = dbviews.HistoryView()
+        queue_controller = viewcontrollers.QueueController(
+            model = self.dbase_model, view = self.queue_view)
+        history_controller = viewcontrollers.HistoryController(
+            model = self.dbase_model, view = self.history_view)
 
+        self.tabstrip.append_page(self.bot_tab,
+                                  guitools.TabIcon(
+                                    label='Bot Status',
+                                    stock=gtk.STOCK_PREFERENCES))
+        self.tabstrip.append_page(self.queue_view,
+                                  guitools.TabIcon(
+                                    label = 'Job Queue',
+                                    stock=gtk.STOCK_INDEX))
+        self.tabstrip.append_page(self.history_view,
+                                  guitools.TabIcon(
+                                    label = 'Job History',
+                                    stock=gtk.STOCK_JUSTIFY_FILL))
 
-        return strip
+        self.body.pack_start(self.tabstrip, True, True, 2)
+        self.add(self.body)
+        self.show_all()
+        self.connect('destroy', gtk.main_quit)
+        self.set_size_request(600,400)
 
-    def _get_bottombar(self):
-        return gtk.Label('Bottom bar')
+        # Force initial display of the queue and history
+        self.dbase_model.RefreshQueue()
+        self.dbase_model.RefreshHistory()
 
-    def _exit(self, widget=None):
-        self._stop_bot = True
-        time.sleep(0.25)
-        self.dbase.close()
-        return gtk.main_quit()
 
-    def _check_db_sanity(self):
-        if self.dbase.verify_tables():
-            self.dbase.CreateTables()
+
+
 
 if __name__ == '__main__':
-    gtk.gdk.threads_init()
-    w = App()
+    model = dbutils.dbase()
+    w = Gui(dbase_model = model)
     w.show_all()
-    gtk.gdk.threads_enter()
     gtk.main()
-

src/vpackager/botcontroller.py

-#!/usr/bin/env python
-
-#    This file is part of vpackager.
-#
-#    vpackager is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License v2 as published by
-#    the Free Software Foundation.
-#
-#    vpackager 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 vpackager.  If not, see <http://www.gnu.org/licenses/>.
-
-import gtk
-import dbutils
-import dbviews
-import guitools
-import viewcontrollers
-
-__author__ = 'M0E-lnx'
-__author_email__ = 'moc.liamg@xnl.E0M'[::-1]
-__version__ = '0.1'
-
-class StatusTab(gtk.VBox):
-    """ Content of the status tab packed in a vbox that can be
-    appended as a page to the tabstrip"""
-
-    def __init__(self):
-        gtk.VBox.__init__(self)
-        self.bot_state_indicators = {}
-        self.bot_control_buttons = {}
-        self.bot_state_indicators['state'] = gtk.Label()
-        self.bot_state_indicators['running_job'] = gtk.Label()
-        self.bot_control_buttons['start'] = guitools.vButton(
-            stock=gtk.STOCK_YES, label='Start')
-        self.bot_control_buttons['stop'] = guitools.vButton(
-            stock=gtk.STOCK_NO, label='Stop')
-        for lb  in self.bot_state_indicators.values():
-            lb.set_use_markup(True)
-        self.pack_start(self._get_top_frames(), False, True, 4)
-        self.pack_start(self._get_output_display(), True, True, 4)
-
-    def _get_top_frames(self):
-        """ Top frames that show the bot status and bot control buttons."""
-        section = gtk.HBox()
-        frm_tl = gtk.Frame('Bot Status')
-        frm_tr = gtk.Frame('Build Bot Control')
-        frm_tl_vbox = gtk.VBox()
-        frm_tl.add(frm_tl_vbox)
-        line1 = gtk.HBox()
-        line2 = gtk.HBox()
-        frm_tl_vbox.pack_start(line1, False, True, 2)
-        frm_tl_vbox.pack_start(line2, False, True, 2)
-        lb_bot_state = gtk.Label('Current Bot State:')
-        lb_running_job = gtk.Label('Current Job:')
-        line1.pack_start(lb_bot_state, False, False, 2)
-        line2.pack_start(lb_running_job, False, False, 2)
-        line1.pack_start(
-            self.bot_state_indicators['state'], True, True, 2)
-        line2.pack_start(
-            self.bot_state_indicators['running_job'], True, True, 2)
-
-        # Pack the bot control widgets
-        rvbox = gtk.VBox()
-        rhbox = gtk.HBox()
-        rvbox.pack_start(rhbox, True, True, 2)
-        rhbox.pack_start(
-            self.bot_control_buttons['start'], False, False, 2)
-        rhbox.pack_start(
-            self.bot_control_buttons['stop'], False, False, 2)
-        frm_tr.add(rvbox)
-
-        section.pack_start(frm_tl, True, True, 2)
-        section.pack_start(frm_tr, False, False, 2)
-
-
-        return section
-
-    def _get_output_display(self):
-        scrl = gtk.ScrolledWindow()
-        scrl.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-        self.textview = gtk.TextView()
-        for edge in (gtk.TEXT_WINDOW_LEFT, gtk.TEXT_WINDOW_RIGHT,
-                     gtk.TEXT_WINDOW_TOP, gtk.TEXT_WINDOW_BOTTOM):
-            self.textview.set_border_window_size(edge, 4)
-        scrl.add(self.textview)
-
-        return scrl
-
-
-class Gui(gtk.Window):
-    """ Main window for vpackager"""
-    def __init__(self, dbase_model):
-        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
-        self.dbase_model = dbase_model
-        self.body = gtk.VBox()
-        self.set_title('vpackager')
-        self.set_position(gtk.WIN_POS_CENTER)
-        self.body.pack_start(gtk.Label('Menu goes here'), False, True, 2)
-        self.tabstrip = guitools.TabStrip()
-        self.bot_tab = StatusTab()
-        self.queue_view = dbviews.QueueView()
-        self.history_view = dbviews.HistoryView()
-        queue_controller = viewcontrollers.QueueController(
-            model = self.dbase_model, view = self.queue_view)
-        history_controller = viewcontrollers.HistoryController(
-            model = self.dbase_model, view = self.history_view)
-
-        self.tabstrip.append_page(self.bot_tab,
-                                  guitools.TabIcon(
-                                    label='Bot Status',
-                                    stock=gtk.STOCK_PREFERENCES))
-        self.tabstrip.append_page(self.queue_view,
-                                  guitools.TabIcon(
-                                    label = 'Job Queue',
-                                    stock=gtk.STOCK_INDEX))
-        self.tabstrip.append_page(self.history_view,
-                                  guitools.TabIcon(
-                                    label = 'Job History',
-                                    stock=gtk.STOCK_JUSTIFY_FILL))
-
-        self.body.pack_start(self.tabstrip, True, True, 2)
-        self.add(self.body)
-        self.show_all()
-        self.connect('destroy', gtk.main_quit)
-        self.set_size_request(600,400)
-
-        # Force initial display of the queue and history
-        self.dbase_model.RefreshQueue()
-        self.dbase_model.RefreshHistory()
-
-
-
-
-
-if __name__ == '__main__':
-    model = dbutils.dbase()
-    w = Gui(dbase_model = model)
-    w.show_all()
-    gtk.main()

src/vpackager/dbviews.py

             bar.pack_start(button, False, False, 2)
         return bar
 
-
-
     def tree_selection_change_event(self, tree=None):
         """ Every time the user clicks on the treeview, update the selected
         job id, so that we can remove it or see details about it"""
+
         model, iter = tree.get_selection().get_selected()
         selrow = model[iter][0]
         self._selectedjob = selrow
+        self.Refresh_Toolbar_Status()
 
     def tree_click_event(self, tree, event, data=None):
         """ Use the tree click event to redirect the calls depending on type of click
         the tree received"""
+        self.Refresh_Toolbar_Status
         if event.button == 1 :
             return
         elif event.button == 3:
+            if not self._selectedjob:
+                return
             # Show the right-click menu
             self.rc_menu.popup(None, None, None, event.button, event.time)
 
     def Update_TableView(self, data=None):
         """ Triggered by the model to refresh the contents of the GUI
         representing the job queue"""
+        self.Refresh_Toolbar_Status()
         model = self.treeview.get_model()
         model.clear()
         for item in data:
             model.append([item[0], item[1], item[2], item[3]])
 
+
+    def Refresh_Toolbar_Status(self):
+        for button in (
+            self.exposed_widgets['remove_job'],
+            self.exposed_widgets['about_job']):
+            button.set_property('sensitive', self._selectedjob is not None)
+
+
 class HistoryView(QueueView):
     """ History manager tree view """
     def __init__(self):
         self.banner_label.set_label('Manager job history')
         self.rc_menu = RCMenu(items=['About Job', 'Rebuild Job'])
 
+    def tree_click_event(self, tree, event, data=None):
+        """ Events triggered with the treeview is clicked """
+        if event.button == 1:
+            return
+        elif event.button == 3:
+            if not self._selectedjob:
+                return
+            self.rc_menu.popup(None, None, None, event.button, event.time)
+
+
+    def Refresh_Toolbar_Status(self):
+        self.exposed_widgets['about_task'].set_property(
+            'sensitive', self._selectedjob is not None)
+        self.exposed_widgets['wipe_history'].set_property(
+            'sensitive', self.treeview.GetRowCount() > 0)
+
     def _get_treeview(self):
         _model = gtk.ListStore(int, str, str, str, str, str, str)
         return guitools.TableTree(
             model.append([
                 item[0], item[1], item[2], item[3], item[4], item[5], item[6]
             ])
+        self.exposed_widgets['wipe_history'].set_property(
+            'sensitive', self.treeview.GetRowCount() > 0)

src/vpackager/guitools.py

 
     def switch_page_event(self, widget=None, page=None, page_num=0):
         """ Actions to be performed with the user changes the visible tab"""
-        pass
+        # Update the toolbars on the treeviews
+        try:
+            self.get_nth_page(1).Refresh_Toolbar_Status()
+            self.get_nth_page(2).Refresh_Toolbar_Status()
+        except:
+            pass
 
 class TabIcon(gtk.VBox):
     """ Custom widget composed of a gtk.VBox that shows an image and label
             i += 1
         self.set_headers_visible(True)
 
+    def GetRowCount(self):
+        return len(self.get_model())
+
 
 class AboutJob(gtk.Dialog):
     '''Dialog used to display detailed information about a build job. The user
         scroll.add(tree)
         return scroll
 
-
-
-
 class About(gtk.AboutDialog):
     """ About dialog for vpackager"""
     def __init__(self):

src/vpackager/viewcontrollers.py

         self.model = model
         self.view = view
         self.setup_observers()
-        #self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
-        #self.window.set_size_request(400, 200)
-        #self.window.connect('destroy', gtk.main_quit)
-        #self.window.add(self.view)
-        # Connect signals
+        self._parentwindow = self.view.get_parent_window()
         self.connect_signals()
         self.refresh_view()
 
 
     def _about_job_event(self, widget=None):
         """ Display a dialog showing detailed information about a queued job"""
-        dia = guitools.AboutJob(parent = None, jobid=self.view._selectedjob)
+        dia = guitools.AboutJob(parent = self._parentwindow,
+                                jobid=self.view._selectedjob)
         if dia.run():
             dia.destroy()
 
 
     def _add_job_event(self, widget=None):
         """ Display a dialog to add a new job to the queue"""
-        dia = guitools.JobAddDialog(parent=self.window)
+        dia = guitools.JobAddDialog(parent=self._parentwindow)
         res = dia.run()
         dia.hide()