Commits

Moises Henriquez committed 1f22c46

Refactoring to follow MVC model

Comments (0)

Files changed (7)

src/vpackager/bot.py

         threading.Thread.__init__(self)
         self.name = 'job-dispatcher'
         self.parent = parent
+
         if dbase:
             self.dbase = dbase
         else:
 
     def _get_next_job(self):
         ''' Get the next job in line from the database '''
-        return self.dbase.GetNextJob()
+        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'''
     def run(self):
         while self.parent._bot_run:
             time.sleep(0.25)
-            njob = self.dbase.GetNextJob()
+            njob = self._get_next_job()
             if njob is None:
                 # NO new job in queue
                 self.parent._bot_run = False
                 except:
                     print 'Error while installing package'
                     pass
+        #self.dbase.close()
 
 class Tab(gtk.VBox):
     ''' Generic tab for use in the tabstrip '''
         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()
-        if not self.jobDispatcher.isAlive():
-            del self.jobDispatcher
-            self.jobDispatcher = Dispatcher(parent=self._parent,
-                    dbase=self.dbase)
-            self.jobDispatcher.start()
-        return
+        # 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."""
 
         self.add(self.body)
 
+    def _get_next_job_(self):
+        return self.dbase.GetNextJob()
+
     def _get_menubar(self):
         mb = gtk.MenuBar()
         filemenu = gtk.Menu()

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/dbutils.py

     done via this class. The database must be closed on exit"""
     def __init__(self, path = dbpath):
         self._dbpath = path
+        self.history_observers = []
+        self.queue_observers = []
+        self.db = None
+        self.cursor = None
+
+    def add_observer(self, obj, table='queue'):
+        """Addn an observer to be notified of any changes to the dbase"""
+        if table == 'queue':
+            self.queue_observers.append(obj)
+        else:
+            self.history_observers.append(obj)
+        return
+
+    def notify(self, data, group='all'):
+        """Notify all observer of the changes that just happened"""
+        if group == 'all':
+            for watcher in self.queue_observers:
+                watcher(data)
+            for watcher in self.history_observers:
+                watcher(data)
+        elif group == 'queue':
+            for watcher in self.queue_observers:
+                watcher(data)
+        elif group == 'history':
+            for watcher in self.history_observers:
+                watcher(data)
+
+    def _open(self):
+        """ Internal method that will be ussed to wrap the connection
+        to single instances and then will be immediately closed.
+        Returns a tuple o f(dbase, cursor)"""
+        if self.db:
+            try:
+                self.db.commit()
+                self.db.close()
+            except:
+                pass
+
         self.db = sqlite3.connect(self._dbpath)
-        self.cursor = self.db.cursor()
-
+        return (self.db, self.db.cursor())
 
     def close(self):
         """ Completely close the database to save all data """
-        self.cursor.close()
-        self.db.close()
+        try:
+            self.db.commit()
+            self.db.close()
+        except:
+            pass
 
     def verify_tables(self):
         """Verify that the essential tables for the vpackages environemtn
         actually exist"""
         self.cursor.execute('''select name from sqlite_master where
-                type="table"''')
+                type="table" and name != "sqlite_sequence"''')
         res = self.cursor.fetchall()
         if len(res) > 0:
             return False
     def CreateTables(self):
         """Create the essential tables for the vpackager environment. Should
         only need to be ran once"""
-        self.cursor.execute('''
+        db,cursor = self._open()
+        cursor.execute('''
         create table queue (id integer primary key autoincrement,
         app text,
         ver text,
         type text,
         install text,
         patches text)''')
-        self.db.commit()
+        db.commit()
 
-        self.cursor.execute('''
+        cursor.execute('''
         create table history(id integer primary key,
         app text, ver text, srcURI text, descURI text,
         release text, type text, patches text, buildstart text,
         buildend text, stdout text, result text)''')
-        self.db.commit()
+        db.commit()
 
-        return
+        return self.close()
 
     def NewJob(self, app, ver, srcURI, descURI, release, _type,
             install, patches):
         """ Add a new job to the job queue"""
         t = (app, ver, srcURI, descURI, release, _type, install, patches)
-        self.cursor.execute('''
+        db, cursor = self._open()
+        cursor.execute('''
         insert into queue (app, ver, srcURI, descURI, release, type,
         install, patches) values (?,?,?,?,?,?,?,?)''', t)
-        self.db.commit()
-        return
+        db.commit()
+        self.notify(data = self.GetQueue(), group='queue')
+        return self.close()
 
     def DeleteJob(self, job_id):
         """ Removes job_id from the job queue"""
+        db, cursor = self._open()
         t = (job_id,)
-        self.cursor.execute('''
+        cursor.execute('''
         delete from queue where id=?''',t)
-        self.db.commit()
-        return
+        db.commit()
+        self.notify(data = self.GetQueue(), group='queue')
+        return self.close()
 
     def LogJobStart(self, job_id):
         """ Moves a job from the queue to the history and adds the timestamp to
         the corresponding to the time the job started"""
+        db, cursor = self._open()
         t = (job_id,)
-        self.cursor.execute('''
+        cursor.execute('''
         select id, app, ver, srcURI, descURI, release, type from queue where
         id=?''',t)
         res = self.cursor.next()
-        self.cursor.execute('''
+        cursor.execute('''
         insert into history (id, app, ver, srcURI, descURI, release, type,
         buildstart, result) values (?,?,?,?,?,?,?,CURRENT_TIMESTAMP,
         'Running')''', res)
-        self.cursor.execute('delete from queue where id=?', t)
-        self.db.commit()
-        return
+        cursor.execute('delete from queue where id=?', t)
+        db.commit()
+        self.notify(data = self.GetHistory(), group='history')
+        return self.close()
 
     def LogJobEnd(self, job_id, stdoutpath, result):
         """ Update the job history to log timestamp when the job ended and the
         result of the build"""
+        db, cursor = self._open()
         t = (job_id,)
-        self.cursor.execute('''
+        cursor.execute('''
         update history set buildend=CURRENT_TIMESTAMP where id=?''',t)
         t = (stdoutpath, job_id)
-        self.cursor.execute('''
+        cursor.execute('''
         update history set stdout=? where id=?''',t)
         t = (result, job_id)
-        self.cursor.execute('''
+        cursor.execute('''
         update history set result=? where id=''',t)
 
-        self.db.commit()
-        return
+        db.commit()
+        self.notify(data = self.GetHistory(), group='history')
+        return self.close()
 
     def GetJobProp(self, job_id, prop):
         """ Returns job_id's property (prop) """
-        self.cursor.execute('''select % from queue where id=%s'''% (prop,
+        db, cursor = self._open()
+        cursor.execute('''select % from queue where id=%s'''% (prop,
             job_id))
         try:
-            ret = self.cursor.next()
+            ret = cursor.next()
         except StopIteration:
             ret = None
         if ret:
             return ret[0]
-        return None
+        self.close()
+        return ret
 
     def GetTaskProp(self, job_id, prop):
         """ Return job_id's (prop) property on a performed task. This
         information comes from the build history"""
-        self.cursor.execute('''select %s from history where id=%s'''% (prop,
+        db, cursor = self._open()
+        cursor.execute('''select %s from history where id=%s'''% (prop,
             job_id))
         try:
-            ret = self.cursor.next()
+            ret = cursor.next()
         except StopIteration:
             ret = None
 
         if ret:
             return ret[0]
-        return None
+        self.close()
+        return ret
 
     def GetTaskDetails(self, job_id):
         """ Returns all information from the job history about job_id in the
         form of a dictionary"""
+        db, cursor = self._open()
         retval = {}
         t = (job_id,)
-        self.cursor.execute('''select * from history where id=?''',t)
+        cursor.execute('''select * from history where id=?''',t)
         try:
-            ret = self.cursor.next()
+            ret = cursor.next()
         except StopIteration:
             ret = None
         if ret:
             retval['result'] = ret[11]
         else:
             retval = {}
+        self.close()
 
         return retval
 
     def GetJobDetails(self, job_id):
         """ Returns all the information from the job queue about job_id in the
         form of a dictionary"""
+        db,cursor = self._open()
         retval = {}
         t = (job_id,)
-        self.cursor.execute('select * from queue where id=?',t)
+        cursor.execute('select * from queue where id=?',t)
         try:
-            ret = self.cursor.next()
+            ret = cursor.next()
         except StopIteration:
             ret = None
 
             retval['patches'] = ret[8]
         else:
             retval = {}
+        self.close()
 
         return retval
 
     def GetNextJob(self):
         """ Find the next job in line to be built.
         Returns the jobid for the next job"""
-        self.cursor.execute('select id from queue')
+        db, cursor = self._open()
+        cursor.execute('select id from queue')
         try:
-            ret = c.next()
+            ret = cursor.next()
         except StopIteration:
             ret = None
+        self.close()
+        return ret
 
-        return ret
+    def RefreshQueue(self):
+        """ Forces the GUI to refresh the queue list"""
+        return self.notify(self.GetQueue(), group='queue')
 
     def GetQueue(self):
         """ Returns a list of all pending jobs """
-        self.cursor.execute('''select id, app, ver, release from queue''')
-        ret = self.cursor.fetchall()
+        db, cursor = self._open()
+        cursor.execute('''select id, app, ver, release from queue''')
+        ret = cursor.fetchall()
+        self.close()
         return ret
 
+    def RefreshHistory(self):
+        """ Forces a push to refresh the GUI representation of the history"""
+        return self.notify(self.GetHistory(), group='history')
+
     def GetHistory(self):
         """ Returns alist of all the processed jobs in the job history"""
-        self.cursor.execute('''
-        select id, app, ver, buildstart, buildend, result from history''')
-        return self.cursor.fetchall()
+        db, cursor = self._open()
+        cursor.execute('''
+        select id, app, ver, release, buildstart, buildend, result from history''')
+
+        ret = cursor.fetchall()
+        self.close()
+        return ret
 
     def WipeHistory(self):
         """ Deletes the entire job history. Only use after confirming with the
         user"""
-        self.cursor.execute('''delete from history where id > 0''')
+        db, cursor = self._open()
+        cursor.execute('''delete from history where id > 0''')
+        self.close()
+        self.notify(data=self.GetHistory(), group='history')
         return
-
-
-
-
-

src/vpackager/dbviews.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 guitools
+import buildutils
+import time
+
+__author__ = 'M0E-lnx'
+__author_email__ = 'moc.liamg@xnl.E0M'[::-1]
+__version__ = '0.1'
+
+class RCMenu(gtk.Menu):
+    """ Menu that pops-up when the treeview is right-clicked"""
+    def __init__(self, items):
+        gtk.Menu.__init__(self)
+        self.menu_actions = {}
+        for item in items:
+            self.menu_actions[
+                item.replace(' ','_').lower()] = gtk.MenuItem(item)
+            mitem = self.menu_actions[item.replace(' ', '_').lower()]
+            mitem.show()
+            self.append(mitem)
+
+    def connect_item(self, item, callback):
+        return self.menu_actions[item].connect('activate', callback)
+
+
+
+class QueueView(gtk.VBox):
+    """ Job manager container"""
+    def __init__(self):
+        gtk.VBox.__init__(self)
+        self.rc_menu = RCMenu(items = [
+            'Job Details', 'Delete Job', 'Edit Job'])
+        self.exposed_widgets = {}
+        self.banner_area = gtk.HBox()
+        self.pack_start(self.banner_area, False, True, 4)
+        self.banner_label = gtk.Label('Manage job queue')
+        self.banner_area.pack_start(self.banner_label, True, True, 2)
+        scrl = gtk.ScrolledWindow()
+        scrl.set_policy(gtk.POLICY_AUTOMATIC,
+                        gtk.POLICY_AUTOMATIC)
+        _model = gtk.ListStore(
+            int, str, str, str)
+        self.treeview = self._get_treeview()
+        self.treeview.connect('button-press-event', self.tree_click_event)
+        self.treeview.connect('cursor-changed', self.tree_selection_change_event)
+        scrl.add(self.treeview)
+        self.pack_start(scrl, True, True, 2)
+        bottombar = self._get_bottom_toolbar()
+        self.pack_start(bottombar, False, True, 2)
+        self._selectedjob = None
+
+    def connect_item(self, item, callback):
+        self.exposed_widgets[item].connect('clicked', callback)
+
+    def _get_treeview(self):
+        _model = gtk.ListStore(int, str, str, str)
+        return guitools.TableTree(
+            model = _model,
+            columns = [
+                'ID',
+                'Application',
+                'Version',
+                'Release'
+            ]
+        )
+
+    def _get_bottom_toolbar(self):
+        bar = gtk.HBox()
+        self.exposed_widgets['add_job'] = guitools.vButton(
+            stock=gtk.STOCK_ADD, label='New Job')
+        self.exposed_widgets['remove_job'] = guitools.vButton(
+            stock=gtk.STOCK_REMOVE, label='Delete Job')
+        self.exposed_widgets['about_job'] = guitools.vButton(
+            stock=gtk.STOCK_INFO, label='About Job')
+
+        for button in self.exposed_widgets.values():
+            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
+
+    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"""
+        if event.button == 1 :
+            return
+        elif event.button == 3:
+            # 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"""
+        model = self.treeview.get_model()
+        model.clear()
+        for item in data:
+            model.append([item[0], item[1], item[2], item[3]])
+
+class HistoryView(QueueView):
+    """ History manager tree view """
+    def __init__(self):
+        QueueView.__init__(self)
+        self.banner_label.set_label('Manager job history')
+        self.rc_menu = RCMenu(items=['About Job', 'Rebuild Job'])
+
+    def _get_treeview(self):
+        _model = gtk.ListStore(int, str, str, str, str, str, str)
+        return guitools.TableTree(
+            model = _model,
+            columns = ['ID',
+                       'Application',
+                       'Version',
+                       'Release',
+                       'Build Start',
+                       'Build End',
+                       'Result']
+        )
+
+    def _get_bottom_toolbar(self):
+        box = gtk.HBox()
+        self.exposed_widgets['about_task'] = guitools.vButton(
+            stock = gtk.STOCK_INFO, label = 'About Task')
+        self.exposed_widgets['wipe_history'] = guitools.vButton(
+            stock = gtk.STOCK_DELETE, label='Delete History')
+
+        for button in self.exposed_widgets.values():
+            box.pack_start(button, False, False, 2)
+
+        return box
+
+    def Update_TableView(self, data=None):
+        """ Update the GUI representing the history"""
+        model = self.treeview.get_model()
+        model.clear()
+        for item in data:
+            model.append([
+                item[0], item[1], item[2], item[3], item[4], item[5], item[6]
+            ])

src/vpackager/guitools.py

 __author_email__ = 'moc.liamg@xnl.E0M'[::-1]
 __version__ = '0.1'
 
+class TabStrip(gtk.Notebook):
+    """Tabstrip used in the main GUI"""
+    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):
+        """ Actions to be performed with the user changes the visible tab"""
+        pass
+
+class TabIcon(gtk.VBox):
+    """ Custom widget composed of a gtk.VBox that shows an image and label
+    and is used for the tabstrip label widgets"""
+    def __init__(self, label='', stock=None):
+        gtk.VBox.__init__(self)
+        lb = gtk.Label()
+        lb.set_use_markup(True)
+        lb.set_markup(label)
+        img = gtk.Image()
+        img.set_from_stock(stock, gtk.ICON_SIZE_BUTTON)
+        self.pack_start(lb, False, False, 2)
+        self.pack_start(img, False, False, 2)
+        self.show_all()
+
+
+class TableTree(gtk.TreeView):
+    """Generic treeview widget that will be used to display the contents of the
+    database tables. Will be used to display both queue and history"""
+    def __init__(self, model, columns = []):
+        gtk.TreeView.__init__(self, model=model)
+        i = 0
+        for col in columns:
+            _col = gtk.TreeViewColumn(col)
+            _cr = gtk.CellRendererText()
+            _col.pack_start(_cr)
+            _col.add_attribute(_cr, 'text', i)
+            _col.set_resizable(True)
+            self.append_column(_col)
+            i += 1
+        self.set_headers_visible(True)
+
+
 class AboutJob(gtk.Dialog):
     '''Dialog used to display detailed information about a build job. The user
     interface will not display every detail about the job, but this dialog
                     self.okButton.set_property('sensitive', enable)
                     self.SrcURI = widget.get_text()
                     return
-
-
-
-

src/vpackager/historymanager.py

 
 class TaskMenu(gtk.Menu):
     """Menu that pops up when right-clicking a task"""
-    def __init__(self, selected, parent=None):
+    def __init__(self, selected, dbase, parent=None):
         gtk.Menu.__init__(self)
         self.selected = selected
         self._parent = parent
+        self.dbase = dbase
         item_rebuild = gtk.MenuItem('Build again')
         item_rebuild.connect('activate',
                 self._rebuild_event)
 
     def _rebuild_event(self, widget=None):
         _id = self.selected
-        _srcuri = dbutils.GetTaskProp(_id, 'srcURI')
+        _srcuri = self.dbase.GetTaskProp(_id, 'srcURI')
+        #_srcuri = dbutils.GetTaskProp(_id, 'srcURI')
         if _srcuri.startswith('/'):
             if not os.path.exists(_srcuri):
                 # FIXME: Source package can no longer be found
                                 "the new location of the source package")
                 if err.run():
                     err.destroy()
-        _descuri = dbutils.GetTaskProp(_id, 'descURI')
-        _patches = dbutils.GetTaskProp(_id, 'patches')
-        _type = dbutils.GetTaskProp(_id, 'type')
-        _app = dbutils.GetTaskProp(_id, 'app')
-        _ver = dbutils.GetTaskProp(_id, 'ver')
+        info = self.dbase.GetTaskDetails(_id)
+        _descuri = info['descURI']
+        _patches = info['patches']
+        _type = info['type']
+        _app = info['app']
+        _ver = info['ver']
         dia = guitools.JobAddDialog()
         dia.SrcURI = _srcuri
         dia.DescURI = _descuri
             srctype = dia.srctype
             toinstall = dia.install
             patches = ','.join(dia.patches)
-            dbutils.NewJob(app, ver, src, desc,
+            self.dbase.NewJob(app, ver, src, desc,
                     rel, srctype, toinstall, patches)
+            #dbutils.NewJob(app, ver, src, desc,
+            #        rel, srctype, toinstall, patches)
         dia.destroy()
 
     def _task_details_event(self, widget=None):
     def _tree_button_press(self, widget, event):
         if event.button == 3:
             m = TaskMenu(selected = self._selected_jobid,
-                    parent=self)
+                    dbase = self.dbase, parent=self)
             m.popup(None, None, None, event.button, event.time)
 
     def _tree_click_event(self, tree=None):

src/vpackager/viewcontrollers.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
+
+__author__ = 'M0E-lnx'
+__author_email__ = 'moc.liamg@xnl.E0M'[::-1]
+__version__ = '0.1'
+
+class QueueController(object):
+    """ Controller for the queue view"""
+    def __init__(self, model, view):
+        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.connect_signals()
+        self.refresh_view()
+
+    def refresh_view(self):
+        self._show_queue()
+
+    def setup_observers(self):
+        self.model.add_observer(
+            self.view.Update_TableView, table='queue')
+        return
+
+    def connect_signals(self):
+        # Connect buttons
+        self.view.connect_item('about_job', self._about_job_event)
+        self.view.connect_item('add_job', self._add_job_event)
+        self.view.connect_item('remove_job', self._delete_job_event)
+        # connect right-click menu items
+        self.view.rc_menu.connect_item('job_details', self._about_job_event)
+        self.view.rc_menu.connect_item('delete_job', self._delete_job_event)
+
+    def _show_queue(self, widget=None):
+        """ Force the queue to be displayed"""
+        return self.model.RefreshQueue()
+
+    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)
+        if dia.run():
+            dia.destroy()
+
+    def _delete_job_event(self, widget=None):
+        return self.model.DeleteJob(self.view._selectedjob)
+
+    def _add_job_event(self, widget=None):
+        """ Display a dialog to add a new job to the queue"""
+        dia = guitools.JobAddDialog(parent=self.window)
+        res = dia.run()
+        dia.hide()
+
+        if res == gtk.RESPONSE_OK:
+            return self.model.NewJob(
+                app = dia.app,
+                ver = dia.ver,
+                srcURI = dia.SrcURI,
+                descURI = dia.DescURI,
+                release = dia.release,
+                _type = dia.srctype,
+                install = dia.install,
+                patches = ','.join(dia.patches))
+        else:
+            dia.destroy()
+            return None
+
+
+class HistoryController(QueueController):
+    """ Controller for the history view """
+    def __init__(self, model, view):
+        self.model = model
+        self.view = view
+        QueueController.__init__(self, self.model, self.view)
+
+    def setup_observers(self):
+        """ Connect observers to the signalers"""
+        self.model.add_observer(self.view.Update_TableView, table='history')
+
+
+    def refresh_view(self):
+        return self.model.RefreshHistory()
+
+    def connect_signals(self):
+        # Connect buttons
+        self.view.connect_item('about_task', self._about_task_event)
+        self.view.connect_item('wipe_history', self._wipe_history_event)
+        # Connect menu items.
+        self.view.rc_menu.connect_item('about_job', self._about_task_event)
+
+    def _about_task_event(self, widget=None):
+        pass
+
+    def _wipe_history_event(self, widget=None):
+        pass
+
+
+
+
+if __name__ == "__main__":
+    model = dbutils.dbase()
+    view = dbviews.HistoryView()
+    control = HistoryController(model = model,
+                              view = view)
+    control.Run()