vpackager / src / vpackager / bot.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
import threading
import Queue
import buildutils
import os
import shutil
import urllib
import time

__author__ = 'M0E-lnx'
__author_email__ = 'moc.liamg@xnl.E0M'[::-1]
__version__ = '0.1'


class Dispatcher(threading.Thread):
    """ Threaded Build process"""
    def __init__(self, model, view):
        threading.Thread.__init__(self)
        self.view = view
        self.model = model

    def get_builder(self, srcuri, _id):
        """ Returns the correct source object based on the source URI"""
        if srcuri.startswith('/'):
            srcob = buildutils.Source(srcuri)
        else:
            # 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 process_source(self, srcuri, workdir):
        """ Copy the source to the right location """
        if srcuri.startswith('/'):
            return shutil.copy2(srcuri, workdir)
        return

    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 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 clear_textview(self):
        buf = self.view.textview.get_buffer()
        start, end = buf.get_bounds()
        return buf.delete(start, end)

    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.view.RUN_BOT:
            time.sleep(1)
            job_id = self.model.GetNextJob()
            job_info = self.model.GetJobDetails(job_id)

        # 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()

        # 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: ^^

        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))
        # 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
        if retval > 0:
            result = 'Fail'
        else:
            result = 'Success'

        # Log the output to a file
        logpath = os.path.join(os.path.dirname(srcob.srclocation),
                               '%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

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, 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)

    def StopBot(self, widget=None):
        self.RUN_BOT = False
        return self.IndicateBotStopped()

    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 _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)

        # 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 PlayOutput(self, data):
        gtk.gdk.threads_enter()
        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.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)

        return self.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(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))

        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()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.