Andres Riancho wrote:
Javier,

    When you finish implementing the auto-update feature in the
w3af-console, could you please send us an email with one file
containing all the changes you've made? It will be easier to review
your changes like that. Thanks!

Regards,
File attached!

Thanks,

--
Javier Andalia - Python Developer

Gorostiaga 2355 - Office 203
Buenos Aires. Argentina

Rapid7 Recipient of Highest Ranking in Vulnerability Management from Gartner 
and Forrester:
http://www.rapid7.com/resources/gartner_marketscope.jsp
http://www.rapid7.com/resources/forrester-wave.jsp
Index: w3af_console
===================================================================
--- w3af_console        (revision 3894)
+++ w3af_console        (revision 3977)
@@ -23,45 +23,80 @@
 except w3afException, w3:
     print 'Something went wrong, w3af failed to init the output manager. 
Exception: ', str(w3)
     sys.exit(-9)
+
+
+usage_doc = '''
+w3af - Web Application Attack and Audit Framework
+
+Usage:
+
+    ./w3af_console -h
+    ./w3af_console -t
+    ./w3af_console [-s <script_file>]
+
+Options:
+
+    -h or --help
+        Display this help message.
+
+    -t or --test-all
+        Runs all test scripts containing an 'assert' sentence.
     
+    -s <script_file> or --script=<script_file>
+        Run <script_file> script.
 
+    -n or --no-update
+        No update check will be made when starting. This option takes 
+        precedence over the 'auto-update' setting in 'startup.conf' file.
+     
+    -f or --force-update
+        An update check will be made when starting. This option takes 
+        precedence over the 'auto-update' setting in 'startup.conf' file.
+    
+    -p <profile> or --profile=<profile>
+        Run with the selecte <profile>
+
+For more info visit http://w3af.sourceforge.net/
+'''
+
 def usage():
-    om.out.information('w3af - Web Application Attack and Audit Framework')
-    om.out.information('')
-    om.out.information('Options:')
-    om.out.information('    -h              Print this help message.')
-    om.out.information('    -s <file>       Execute a script file.')
-    om.out.information('    -p <profile>    Run with the selected profile')
-    om.out.information('')
-    om.out.information('http://w3af.sourceforge.net/')
+    om.out.information(usage_doc)
 
 def main():
     try:
-        opts, args = getopt.getopt(sys.argv[1:], "p:i:hs:get", [] )
-    except getopt.GetoptError:
+        long_options = ['script=', 'help', 'test-all', 'no-update',
+                        'force-update', 'profile=']
+        opts, args = getopt.getopt(sys.argv[1:], "ehts:nfp:", long_options)
+    except getopt.GetoptError, e:
         # print help information and exit:
         usage()
         return -3
     scriptFile = None
     profile = None
+    doupdate = None
+    
     for o, a in opts:
-        if o in ( "-e"  ):
+        if o == "-e":
             # easter egg
             import base64
-            om.out.information( base64.b64decode( 
'R3JhY2lhcyBFdWdlIHBvciBiYW5jYXJtZSB0YW50YXMgaG9yYXMgZGUgZGVzYXJyb2xsbywgdGUgYW1vIGdvcmRhIQ=='
 ) )
-        if o in ( "-t"  ):
+            om.out.information( 
base64.b64decode('R3JhY2lhcyBFdWdlIHBvciBiYW5jYXJtZSB0YW50YXMgaG9yYXMgZGUgZGVzYXJyb2xsbywgdGUgYW1vIGdvcmRhIQ=='))
+        if o in ('-t', '--test-all'):
             # Test all scripts that have an assert call
             from core.controllers.misc.w3afTest import w3afTest
             w3afTest()
             return 0
-        if o == "-s":
+        if o in ('-s', '--script'):
             scriptFile = a
         if o in ('-p', '--profile'):
             # selected profile
             profile = a
-        if o == "-h":
+        if o in ('-h', '--help'):
             usage()
             return 0
+        if o in ('-f', '--force-update'):
+            doupdate = True
+        elif o in ('-n', '--no-update'):
+            doupdate = False
     
     # console
     from core.ui.consoleUi.consoleUi import consoleUi
@@ -77,13 +112,13 @@
             commandsToRun = []
             for line in fd:   
                 line = line.strip()
-                if line != '' and line[0] != '#':   # if not a comment..
+                if line != '' and line[0] != '#': # if not a comment..
                     commandsToRun.append( line )
             fd.close() 
     elif profile is not None:
         commandsToRun = ["profiles use %s" % profile]
 
-    console = consoleUi(commands=commandsToRun)
+    console = consoleUi(commands=commandsToRun, do_upd=doupdate)
     console.sh()
 
 
@@ -91,4 +126,3 @@
     errCode = main()
     backToCurrentDir()
     sys.exit(errCode)
-
Index: core/controllers/misc/dependencyCheck.py
===================================================================
--- core/controllers/misc/dependencyCheck.py    (revision 3894)
+++ core/controllers/misc/dependencyCheck.py    (revision 3977)
@@ -22,7 +22,6 @@
 
 import core.controllers.outputManager as om
 import sys
-import subprocess
 
 # Use w3af's 'extlib' modules first. By doing this we ensure that modules
 # in 'w3af/extlib/' are first imported over python installation 
'site-packages/'
@@ -95,5 +94,13 @@
         msg += '    - On Debian based distributions: apt-get install 
python-lxml'
         print msg
         sys.exit( 1 )
+    
+    try:
+        import pysvn
+    except:
+        msg = 'You have to install python pysvn lib. \n'
+        msg += '    - On Debian based distributions:  apt-get install 
python-svn'
+        print msg
+        sys.exit( 1 )
 
 
Index: core/controllers/auto_update/auto_update.py
===================================================================
--- core/controllers/auto_update/auto_update.py (revision 0)
+++ core/controllers/auto_update/auto_update.py (revision 3977)
@@ -0,0 +1,657 @@
+'''
+auto_update.py
+
+Copyright 2011 Andres Riancho
+
+This file is part of w3af, w3af.sourceforge.net .
+
+w3af is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation version 2 of the License.
+
+w3af 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 w3af; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+'''
+
+from __future__ import with_statement
+from datetime import datetime, date, timedelta
+import os
+import time
+import ConfigParser
+import threading
+
+
+class SVNError(Exception):
+    pass
+
+
+class SVNUpdateError(SVNError):
+    pass
+
+
+class SVNCommitError():
+    pass
+
+
+class SVNClient(object):
+    '''
+    Typically an abstract class. Intended to define behaviour. Not to be
+    instantiated.
+    SVN implementations should extend from this class.
+    Implemented methods in child classes should potentially raise SVNError
+    exception (or descendant) when a condition error is found.
+    '''
+
+    def __init__(self, localpath):
+        self._localpath = localpath
+        self._repourl = self._get_repourl()
+        # Action Locker! 
+        self._actionlock = threading.RLock()
+
+    def _get_repourl(self):
+        '''
+        Get repo's URL. To be implemented by subclasses.
+        '''
+        raise NotImplementedError
+
+    def update(self):
+        '''
+        Update local repo to last revision.
+        '''
+        raise NotImplementedError
+
+    def commit(self):
+        '''
+        Commit to remote repo changes in local repo.
+        '''
+        raise NotImplementedError
+
+    def status(self, path=None):
+        '''
+        Return a SVNFilesList object.
+        
+        @param localpath: Path to get the status from. If None use project's
+            root.
+        '''
+        raise NotImplementedError
+
+    def list(self, path_or_url=None):
+        '''
+        Return a SVNFilesList. Elements are tuples containing the path and
+        the status for all files in `path_or_url` at the provided revision.
+        '''
+        raise NotImplementedError
+
+    def diff(self, localpath, rev=None):
+        '''
+        Return string with the differences between `rev` and HEAD revision for
+        `localpath`
+        '''
+        raise NotImplementedError
+
+
+
+import pysvn
+
+# Actions on files
+FILE_UPD = 'UPD' # Updated
+FILE_NEW = 'NEW' # New
+FILE_DEL = 'DEL' # Removed
+
+wcna = pysvn.wc_notify_action
+pysvn_action_translator = {
+    wcna.update_add: FILE_NEW,
+    wcna.update_delete: FILE_DEL,
+    wcna.update_update: FILE_UPD
+}
+
+# Files statuses
+ST_CONFLICT = 'C'
+ST_NORMAL = 'N'
+ST_UNVERSIONED = 'U'
+ST_MODIFIED = 'M'
+ST_UNKNOWN = '?'
+
+wcsk = pysvn.wc_status_kind
+pysvn_status_translator = {
+    wcsk.conflicted: ST_CONFLICT,
+    wcsk.normal: ST_NORMAL,
+    wcsk.unversioned: ST_UNVERSIONED,
+    wcsk.modified: ST_MODIFIED
+}
+
+
+class W3afSVNClient(SVNClient):
+    '''
+    Our wrapper for pysvn.Client class.
+    '''
+
+    UPD_ACTIONS = (wcna.update_add, wcna.update_delete, wcna.update_update)
+
+    def __init__(self, localpath):
+        self._svnclient = pysvn.Client()
+        # Call parent's __init__
+        super(W3afSVNClient, self).__init__(localpath)
+        # Set callback function
+        self._svnclient.callback_notify = self._register
+        # Events occurred in current action
+        self._events = []
+    
+    def __getattribute__(self, name):
+        '''
+        Wrap all methods on order to be able to respond to Ctrl+C signals.
+        This implementation was added due to limitations in pysvn.
+        '''
+        def new_meth(*args, **kwargs):
+            def wrapped_meth(*args, **kwargs):
+                try:
+                    self._result = meth(*args, **kwargs)
+                    self._exc = None
+                except Exception, exc:
+                    if isinstance(exc, pysvn.ClientError):
+                        exc = SVNError(*exc.args)
+                    self._exc = exc
+            # Run wrapped_meth in new thread.
+            th = threading.Thread(target=wrapped_meth, args=args,
+                                  kwargs=kwargs)
+            th.setDaemon(True)
+            try:
+                th.start()
+                while th.isAlive():
+                    time.sleep(0.2)
+                try:
+                    if self._exc:
+                        raise self._exc
+                    return self._result
+                finally:
+                    self._exc = self._result = None
+            except KeyboardInterrupt:
+                raise
+        
+        attr = SVNClient.__getattribute__(self, name)
+        if callable(attr):
+            meth = attr
+            return new_meth
+        return attr
+
+    @property
+    def URL(self):
+        return self._repourl
+
+    def update(self):
+        with self._actionlock:
+            self._events = []
+            try:
+                pysvn_rev = self._svnclient.update(self._localpath)[0]
+            except pysvn.ClientError, ce:
+                raise SVNUpdateError(*ce.args)
+            else:
+                updfiles = self._filter_files(self.UPD_ACTIONS)
+                updfiles.rev = Revision(pysvn_rev.number, pysvn_rev.date)
+                return updfiles
+
+    def status(self, localpath=None):
+        with self._actionlock:
+            path = localpath or self._localpath
+            entries = self._svnclient.status(path, recurse=False)            
+            res = [(ent.path, pysvn_status_translator.get(ent.text_status,
+                                          ST_UNKNOWN)) for ent in entries]
+            return SVNFilesList(res)
+
+    def list(self, path_or_url=None):
+        with self._actionlock:
+            if not path_or_url:
+                path_or_url = self._localpath
+            entries = self._svnclient.list(path_or_url, recurse=False)
+            res = [(ent.path, None) for ent, _ in entries]
+            return SVNFilesList(res)
+
+    def diff(self, localpath, rev=None):
+        with self._actionlock:
+            path = os.path.join(self._localpath, localpath)
+            # If no rev is passed the compare to HEAD
+            if rev is None:
+                rev = pysvn.Revision(pysvn.opt_revision_kind.head)
+            tempfile = os.tempnam()
+            diff_str = self._svnclient.diff(tempfile, path, revision1=rev)
+            return diff_str
+
+    def log(self, start_rev, end_rev):
+        '''
+        Return SVNLogList of log messages between `start_rev`  and `end_rev`
+        revisions.
+        
+        @param start_rev: Revision object
+        @param end_rev: Revision object
+        '''
+        with self._actionlock:
+            # Expected by pysvn.Client.log method
+            pysvnstartrev = pysvn.Revision(pysvn.opt_revision_kind.number, 
+                               start_rev.number)
+            pysvnendrev = pysvn.Revision(pysvn.opt_revision_kind.number,
+                                         end_rev.number)
+            logs = (l.message for l in self._svnclient.log(
+                                                self._localpath,
+                                                revision_start=pysvnstartrev,
+                                                revision_end=pysvnendrev))
+            rev = end_rev if (end_rev.number > start_rev.number) else start_rev
+            return SVNLogList(logs, rev)
+
+
+    def _get_repourl(self):
+        '''
+        Get repo's URL.
+        '''
+        svninfo = self._get_svn_info(self._localpath)
+        return svninfo.URL
+
+    def _get_svn_info(self, path_or_url):
+        try:
+            return self._svnclient.info2(path_or_url, recurse=False)[0][1]
+        except pysvn.ClientError, ce:
+            raise SVNUpdateError(*ce.args)
+
+    def get_revision(self, local=True):
+        '''
+        Return Revision object.
+        
+        @param local: If true return local's revision data; otherwise use
+        repo's.
+        '''
+        path_or_url = self._localpath if local else self._repourl
+        _rev = self._get_svn_info(path_or_url).rev
+        return Revision(_rev.number, _rev.date)
+
+    def _filter_files(self, filterbyactions=()):
+        '''
+        Filter... Return files-actions
+        @param filterby: 
+        '''
+        files = SVNFilesList()
+        for ev in self._events:
+            action = ev['action']
+            if action in filterbyactions:
+                path = ev['path']
+                # We're not interested on reporting directories unless a 
+                # 'delete' has been performed on them
+                if not os.path.isdir(path) or action == wcna.update_delete:
+                    files.append(path, pysvn_action_translator[action])
+        return files
+
+    def _register(self, event):
+        '''
+        Callback method. Registers all events taking place during this action.
+        '''
+        self._events.append(event)
+
+
+class Revision(object):
+    '''
+    Our own class for revisions.
+    '''
+
+    def __init__(self, number, date):
+        self._number = number
+        self._date = date
+
+    def __eq__(self, rev):
+        return self._number == rev.number and \
+                self._date == rev.date
+
+    def __lt__(self, rev):
+        return self._number < rev.number
+
+    @property
+    def date(self):
+        return self._date
+
+    @property
+    def number(self):
+        return self._number
+
+
+# Limit of lines to SVNList types. To be used in __str__ method re-definition.
+PRINT_LINES = 20
+
+class SVNList(list):
+
+    '''
+    Wrapper for python list type. It may contain the number of the current
+    revision and do a custom list print. Child classes are encourage to 
+    redefine the __str__ method.
+    '''
+
+    def __init__(self, seq=(), rev=None):
+        '''
+        @param rev: Revision object
+        '''
+        list.__init__(self, seq)
+        self._rev = rev
+        self._sorted = True
+
+    def _getrev(self):
+        return self._rev
+
+    def _setrev(self, rev):
+        self._rev = rev
+
+    # TODO: Cannot use *full* decorators as we're still on py2.5
+    rev = property(_getrev, _setrev)
+
+    def __eq__(self, olist):
+        return list.__eq__(self, olist) and self._rev == olist.rev
+
+
+class SVNFilesList(SVNList):
+    '''
+    Custom SVN files list holder.
+    '''
+
+    def __init__(self, seq=(), rev=None):
+        SVNList.__init__(self, seq, rev)
+        self._sorted = True
+
+    def append(self, path, status):
+        list.append(self, (path, status))
+        self._sorted = False
+
+    def __str__(self):
+        # First sort by status
+        sortfunc = lambda x, y: cmp(x[1], y[1])
+        self.sort(cmp=sortfunc)
+        lines, rest = self[:PRINT_LINES], max(len(self) - PRINT_LINES, 0)
+        print_list = ['%s %s' % (f, s) for s, f in lines]
+        if rest:
+            print_list.append('and %d files more.' % rest)
+        if self._rev:
+            print_list.append('At revision %s.' % self._rev.number)
+        return os.linesep.join(print_list)
+
+
+class SVNLogList(SVNList):
+    '''
+    Provides a custom way to print a SVN logs list.
+    '''
+    def __str__(self):
+        print_list = []
+        if self._rev:
+            print_list.append('Revision %s:' % self._rev.number)
+        lines, rest = self[:PRINT_LINES], max(len(self) - PRINT_LINES, 0)
+        print_list += ['%3d. %s' % (n + 1, ln) for n, ln in enumerate(lines)]
+        if rest:
+            print_list.append('and %d commit logs more.' % rest)
+        return os.linesep.join(print_list)
+
+
+# Use this class to perform svn actions on code
+SVNClientClass = W3afSVNClient
+
+
+# Facade class. Intended to be used to to interact with the module
+class VersionMgr(object): #TODO: Make it singleton?
+
+    # Events constants
+    ON_UPDATE = 1
+    ON_CONFIRM_UPDATE = 2
+    ON_UPDATE_CHECK = 3
+    ON_COMMIT = 4
+
+    def __init__(self, localpath, log):
+        '''
+        W3af version manager class. Handles the logic concerning the 
+        automatic update/commit process of the code.
+        
+        @param localpath: Working directory
+        @param log: Default output function
+        '''
+        self._localpath = localpath
+
+        self._log = log
+        self._client = SVNClientClass(localpath)
+        # Registered functions
+        self._reg_funcs = {}
+        # Startup configuration
+        self._start_cfg = StartUpConfig()
+
+
+    def update(self, force=False, askvalue=None, print_result=False,
+               show_log=False):
+        '''
+        Perform code update if necessary.
+        
+        @param askvalue: Callback function that will output the update 
+            confirmation response.
+        @param print_result: If True print the result files using instance's
+            log function.
+        @param show_log: If True interact with the user through `askvalue` and
+            show a summary of the log messages.
+        '''
+        client = self._client
+        lrev = client.get_revision(local=True)
+        files = SVNFilesList(rev=lrev)
+
+        if force or self._has_to_update():
+            self._notify(VersionMgr.ON_UPDATE)
+            rrev = client.get_revision(local=False)
+
+            # If local rev is not lt repo's then we got nothing to update.
+            if not (lrev < rrev):
+                return files
+
+            proceed_upd = True
+            # Call callback function
+            if askvalue:
+                proceed_upd = askvalue(\
+                'Your current w3af installation is r%s. Do you want to ' \
+                'update to r%s [y/N]? ' % (lrev.number, rrev.number))
+                proceed_upd = (proceed_upd.lower() == 'y')
+
+            if proceed_upd:
+                msg = 'w3af is updating from the official SVN server...'
+                self._notify(VersionMgr.ON_UPDATE, msg)
+                # Find new deps.
+                newdeps = self._added_new_dependencies()
+                if newdeps:
+                    msg = 'At least one new dependency (%s) was included in ' \
+                    'w3af. Please update manually.' % str(', '.join(newdeps))
+                    self._notify(VersionMgr.ON_UPDATE, msg)
+                else:
+                    # Finally do the update!
+                    files = client.update()
+                    # Now save today as last-update date and persist it.
+                    self._start_cfg.last_upd = date.today()
+                    self._start_cfg.save()
+
+            # Before returning perform some interaction with the user if
+            # requested.
+            if print_result:
+                self._log(str(files))
+    
+            if show_log:
+                show_log = askvalue('Do you want to see a summary of the ' \
+                'new code commits log messages? [y/N]? ').lower() == 'y'
+                if show_log:
+                    self._log(str(self._client.log(lrev, rrev)))
+        return files
+
+    def status(self, path=None):
+        return self._client.status(path)
+
+    def register(self, eventname, func, msg):
+        funcs = self._reg_funcs.setdefault(eventname, [])
+        funcs.append((func, msg))
+
+    def _notify(self, event, msg=None):
+        '''
+        Call registered functions for event. If `msg` is not None then force
+        to call the registered functions with `msg`.
+        '''
+        for f, _msg in self._reg_funcs.get(event, []):
+            f(msg or _msg)
+
+    def _added_new_dependencies(self):
+        '''
+        Return tuple with the dependencies added to extlib/ in the repo if
+        any. Basically it compares local dirs under extlib/ to those in the
+        repo as well as checks if at least a new sentence containing the 
+        import keyword was added to the dependencyCheck.py file.
+        '''
+        #
+        # Check if a new directory was added to repo's extlib
+        #
+        client = self._client
+        ospath = os.path
+        join = ospath.join
+        # Find dirs in repo
+        repourl = self._client.URL + '/' + 'extlib'
+        # In repo we distinguish dirs from files by the dot (.) presence
+        repodirs = (ospath.basename(d) for d, _ in client.list(repourl)[1:] \
+                                        if ospath.basename(d).find('.') == -1)
+        # Get local dirs
+        extliblocaldir = join(self._localpath, 'extlib')
+        extlibcontent = (join(extliblocaldir, f) for f in \
+                                                os.listdir(extliblocaldir))
+        localdirs = (ospath.basename(d) for d in extlibcontent \
+                                                        if ospath.isdir(d))
+        # New dependencies
+        deps = tuple(set(repodirs).difference(localdirs))
+
+        #
+        # Additional constraint: We should verify that at least an import
+        # sentence was added to the dependencyCheck.py file
+        #
+        if deps:
+            depcheck_fpath = 'core/controllers/misc/dependencyCheck.py'
+            diff_str = client.diff(depcheck_fpath)
+            # SVN shows HEAD rev's new lines preceeded by a '-' char.
+            newlineswithimport = \
+                [nl for nl in diff_str.split('\n') \
+                        if nl.startswith('-') and nl.find('import') != -1]
+            # Ok, no import sentence was detected so no dep. was *really*
+            # added.
+            if not newlineswithimport:
+                deps = ()
+
+        return deps
+    
+    def _has_to_update(self):
+        '''
+        Helper method that figures out if an update should be performed
+        according to the startup cfg file.
+        Some rules:
+            1) IF auto_upd is False THEN return False
+            2) IF last_upd == 'yesterday' and freq == 'D' THEN return True
+            3) IF last_upd == 'two_days_ago' and freq == 'W' THEN return False.
+        @return: Boolean value.
+        '''
+        startcfg = self._start_cfg
+        # That's it!
+        if not startcfg.auto_upd:
+            return False
+        else:        
+            freq = startcfg.freq
+            diff_days = max((date.today()-startcfg.last_upd).days, 0)
+            
+            if (freq == StartUpConfig.FREQ_DAILY and diff_days > 0) or \
+                (freq == StartUpConfig.FREQ_WEEKLY and diff_days > 6) or \
+                (freq == StartUpConfig.FREQ_MONTHLY and diff_days > 29):
+                return True
+            return False
+
+
+from core.controllers.misc.homeDir import get_home_dir
+
+class StartUpConfig(object):
+    '''
+    Wrapper class for ConfigParser.ConfigParser.
+    Holds the configuration for the VersionMgr update/commit process
+    '''
+
+    ISO_DATE_FMT = '%Y-%m-%d'
+    # Frequency constants
+    FREQ_DAILY = 'D' # [D]aily
+    FREQ_WEEKLY = 'W' # [W]eekly
+    FREQ_MONTHLY = 'M' # [M]onthly
+
+    def __init__(self):
+        
+        self._start_cfg_file = os.path.join(get_home_dir(), 'startup.conf')
+        self._start_section = 'StartConfig'
+        defaults = {'auto-update': 'true', 'frequency': 'D', 
+                    'last-update': 'None'}
+        self._config = ConfigParser.ConfigParser(defaults)
+        self._autoupd, self._freq, self._lastupd = self._load_cfg()
+
+    ### PROPERTIES #
+
+    def _get_last_upd(self):
+        '''
+        Getter method.
+        '''
+        return self._lastupd
+
+    def _set_last_upd(self, datevalue):
+        '''
+        @param datevalue: datetime.date value
+        '''
+        self._lastupd = datevalue
+        self._config.set(self._start_section, 'last-update',
+                         datevalue.isoformat())
+
+    # TODO: Cannot use *full* decorators as we're still on py2.5
+    # Read/Write property
+    last_upd = property(_get_last_upd, _set_last_upd)
+
+    @property
+    def freq(self):
+        return self._freq
+
+    @property
+    def auto_upd(self):
+        return self._autoupd
+
+    ### METHODS #
+
+    def _load_cfg(self):
+        '''
+        Loads configuration from config file.
+        '''
+        config = self._config
+        startsection = self._start_section
+        if not config.has_section(startsection):
+            config.add_section(startsection)
+
+        # Read from file
+        config.read(self._start_cfg_file)
+
+        auto_upd = config.get(startsection, 'auto-update', raw=True)
+        boolvals = {'false': 0, 'off': 0, 'no': 0,
+                    'true': 1, 'on': 1, 'yes': 1}
+        auto_upd = bool(boolvals.get(auto_upd.lower(), False))
+
+        freq = config.get(startsection, 'frequency', raw=True).upper()
+        if freq not in (StartUpConfig.FREQ_DAILY, StartUpConfig.FREQ_WEEKLY,
+                        StartUpConfig.FREQ_MONTHLY):
+            freq = StartUpConfig.FREQ_DAILY
+
+        lastupdstr = config.get(startsection, 'last-update', raw=True).upper()
+        # Try to parse it
+        try:
+            lastupd = datetime.strptime(lastupdstr, self.ISO_DATE_FMT).date()
+        except:
+            # Provide default value that enforces the update to happen
+            lastupd = date.today() - timedelta(days=31)
+        return (auto_upd, freq, lastupd)
+
+    def save(self):
+        '''
+        Saves current values to cfg file
+        '''
+        with open(self._start_cfg_file, 'wb') as configfile:
+            self._config.write(configfile)
Index: core/controllers/auto_update/tests/test_auto_update.py
===================================================================
--- core/controllers/auto_update/tests/test_auto_update.py      (revision 0)
+++ core/controllers/auto_update/tests/test_auto_update.py      (revision 3977)
@@ -0,0 +1,193 @@
+'''
+Created on Jan 10, 2011
+
+@author: jandalia
+'''
+
+#### TODO: REMOVE ME BEFORE COMMIT!!! ##
+import sys
+sys.path.append('/home/jandalia/workspace2/w3af')
+########################################
+
+from pymock import PyMockTestCase, method, override, dontcare, set_count
+
+from core.controllers.auto_update.auto_update import W3afSVNClient, Revision, \
+    VersionMgr, SVNFilesList, StartUpConfig, FILE_UPD, FILE_NEW, FILE_DEL, \
+    ST_CONFLICT, ST_MODIFIED, ST_UNKNOWN
+
+del W3afSVNClient.__getattribute__
+
+REPO_URL = 'http://localhost/svn/w3af'
+LOCAL_PATH = '/home/user/w3af'
+
+
+def dummy(*args, **kwargs):
+    pass
+
+
+class TestW3afSVNClient(PyMockTestCase):
+    
+    rev = Revision(112, None)
+    upd_files = SVNFilesList(
+        [('file1.txt', FILE_NEW),
+         ('file2.py', FILE_UPD),
+         ('file3.jpg', FILE_DEL)],
+         rev
+        )
+
+    def setUp(self):
+        PyMockTestCase.setUp(self)
+        
+        W3afSVNClient._get_repourl = dummy
+        self.client = W3afSVNClient(LOCAL_PATH)
+        self.client._repourl = REPO_URL
+        self.client._svnclient = self.mock()
+
+    def test_has_repourl(self):
+        self.assertTrue(self.client._repourl is not None)
+
+    def test_has_svn_client(self):
+        self.assertTrue(self.client._svnclient is not None)
+
+    def test_has_localpath(self):
+        self.assertTrue(self.client._localpath is not None)
+
+    def test_upd(self):
+        client = self.client
+        method(client._svnclient, 
'update').expects(LOCAL_PATH).returns([self.rev])
+        override(client, '_filter_files').expects(client.UPD_ACTIONS)
+        self.returns(self.upd_files)
+        ## Stop recording. Play!
+        self.replay()
+        self.assertEquals(self.upd_files, client.update())
+        ## Verify ##
+        self.verify()
+
+    def test_upd_fail(self):
+        from pysvn import ClientError
+        from core.controllers.auto_update.auto_update import SVNUpdateError
+        client = self.client
+        method(client._svnclient, 'update').expects(LOCAL_PATH)
+        self.raises(ClientError('file locked'))
+        ## Stop recording. Play!
+        self.replay()
+        self.assertRaises(SVNUpdateError, client.update)
+        ## Verify ##
+        self.verify()
+        
+    def test_upd_conflict(self):
+        '''
+        Files in conflict exists after update.
+        '''
+        pass
+
+    def test_upd_nothing_to_update(self):
+        '''No update to current copy was made. Tell the user. Presumably 
+        the revision was incremented'''
+        pass
+
+    def test_filter_files(self):
+        import pysvn
+        from pysvn import wc_notify_action as wcna
+        from pysvn import Revision
+        from core.controllers.auto_update.auto_update import os
+        client = self.client
+        override(os.path, 'isdir').expects(dontcare()).returns(False)
+        set_count(exactly=2)
+        ## Stop recording. Play!
+        self.replay()
+        # Call client's callback function several times
+        f1 = '/path/to/file/foo.py'
+        ev = {'action': wcna.update_delete,
+               'error': None, 'mime_type': None, 'path': f1,
+               'revision': Revision(pysvn.opt_revision_kind.number, 11)}
+        client._register(ev)
+
+        f2 = '/path/to/file/foo2.py'        
+        ev2 = {'action': wcna.update_update,
+               'error': None, 'mime_type': None,
+               'path': f2,
+               'revision': Revision(pysvn.opt_revision_kind.number, 11)}
+        client._register(ev2)
+        
+        expected_res = SVNFilesList([(f1, FILE_DEL), (f2, FILE_UPD)])
+        self.assertEquals(expected_res, 
+            client._filter_files(filterbyactions=W3afSVNClient.UPD_ACTIONS))
+        ## Verify ##
+        self.verify()
+
+    def test_status(self):
+        from pysvn import wc_status_kind as wcsk
+        client = self.client
+        # Mock pysvnstatus objects
+        smock = self.mock()
+        smock.path
+        self.setReturn('/some/path/foo')
+        smock.text_status
+        self.setReturn(wcsk.modified)
+        
+        smock2 = self.mock()
+        smock2.path
+        self.setReturn('/some/path/foo2')
+        smock2.text_status
+        self.setReturn(wcsk.conflicted)
+        
+        smock3 = self.mock()
+        smock3.path
+        self.setReturn('/some/path/foo3')
+        smock3.text_status
+        self.setReturn('some_weird_status')
+        
+        status_files = [smock, smock2, smock3]
+        method(client._svnclient, 'status').expects(LOCAL_PATH, recurse=False)
+        self.returns(status_files)
+        ## Stop recording - Replay ##
+        self.replay()
+        expected_res = \
+            SVNFilesList([
+                ('/some/path/foo', ST_MODIFIED),
+                ('/some/path/foo2', ST_CONFLICT),
+                ('/some/path/foo3', ST_UNKNOWN)])
+        self.assertEquals(expected_res, client.status())
+        ## Verify ##
+        self.verify()
+
+    def test_commit(self):
+        pass
+
+
+class TestVersionMgr(PyMockTestCase):
+    
+    def setUp(self):
+        PyMockTestCase.setUp(self)
+        # Override auto_update module variable
+        import core.controllers.auto_update.auto_update as autoupdmod
+        autoupdmod.SVNClientClass = self.mock()
+        self.vmgr = VersionMgr(LOCAL_PATH, dummy)
+    
+    def test_has_to_update(self):
+        
+        vmgr = self.vmgr
+        start_cfg_mock = self.mock()
+        vmgr._start_cfg = start_cfg_mock
+        
+        # Test no auto-update
+        start_cfg_mock.auto_upd
+        self.setReturn(False)
+        self.replay()
+        self.assertFalse(vmgr._has_to_update())
+
+        # Test [D]aily, [W]eekly and [M]onthly auto-update
+        import datetime
+        SC = StartUpConfig        
+        for freq, diffdays in ((SC.FREQ_DAILY, 1), (SC.FREQ_WEEKLY, 8), \
+                               (SC.FREQ_MONTHLY, 34)):
+            self.reset()
+            start_cfg_mock.auto_upd
+            self.setReturn(True)
+            start_cfg_mock.freq
+            self.setReturn(freq)
+            start_cfg_mock.last_upd
+            self.setReturn(datetime.date.today() - 
datetime.timedelta(days=diffdays))
+            self.replay()
+            self.assertTrue(vmgr._has_to_update())
Index: core/controllers/auto_update/tests/__init__.py
===================================================================
Index: core/controllers/auto_update/__init__.py
===================================================================
--- core/controllers/auto_update/__init__.py    (revision 0)
+++ core/controllers/auto_update/__init__.py    (revision 3977)
@@ -0,0 +1,2 @@
+from auto_update import *
+__all__ = ['VersionMgr']
\ No newline at end of file
Index: core/ui/consoleUi/consoleUi.py
===================================================================
--- core/ui/consoleUi/consoleUi.py      (revision 3894)
+++ core/ui/consoleUi/consoleUi.py      (revision 3977)
@@ -23,8 +23,11 @@
 import sys
 try:
     from shlex import *
-    import os.path
+    import os
+    import random
     import traceback
+
+    from core.controllers.auto_update import VersionMgr, SVNError
     from core.ui.consoleUi.rootMenu import *
     from core.ui.consoleUi.callbackMenu import *
     from core.ui.consoleUi.util import *
@@ -35,10 +38,10 @@
     import core.controllers.outputManager as om
     import core.controllers.miscSettings as miscSettings
     from core.controllers.w3afException import w3afException
-    import random
 except KeyboardInterrupt:
     sys.exit(0)
 
+
 class consoleUi:
     '''
     This class represents the console. 
@@ -47,38 +50,57 @@
     @author Alexander Berezhnoy (alexander.berezhnoy |at| gmail.com)
     '''
 
-    def __init__(self, commands=[], parent=None):
+    def __init__(self, commands=[], parent=None, do_upd=None):
         self._commands = commands 
         self._line = [] # the line which is being typed
         self._position = 0 # cursor position
         self._history = historyTable() # each menu has array of (array, 
positionInArray)
         self._trace = []
+        self._upd_avail = False
 
-        self._handlers = { '\t' : self._onTab, \
-            '\r' : self._onEnter, \
-            term.KEY_BACKSPACE : self._onBackspace, \
-            term.KEY_LEFT : self._onLeft, \
-            term.KEY_RIGHT : self._onRight, \
-            term.KEY_UP : self._onUp, \
-            term.KEY_DOWN : self._onDown, \
-            '^C' : self._backOrExit, \
+        self._handlers = {
+            '\t' : self._onTab,
+            '\r' : self._onEnter,
+            term.KEY_BACKSPACE : self._onBackspace,
+            term.KEY_LEFT : self._onLeft,
+            term.KEY_RIGHT : self._onRight,
+            term.KEY_UP : self._onUp,
+            term.KEY_DOWN : self._onDown,
+            '^C' : self._backOrExit,
             '^D' : self._backOrExit,
             '^L' : self._clearScreen,
             '^W' : self._delWord,
             '^H' : self._onBackspace,
             '^A' : self._toLineStart,
-            '^E' : self._toLineEnd } 
+            '^E' : self._toLineEnd
+        }
 
         if parent:
             self.__initFromParent(parent)
         else:
-            self.__initRoot()
+            self.__initRoot(do_upd)
 
-
-    def __initRoot(self):
+    def __initRoot(self, do_upd):
+        '''
+        Root menu init routine.
+        '''
         self._w3af = core.controllers.w3afCore.w3afCore()
         self._w3af.setPlugins(['console'], 'output')
-       
+        
+        if do_upd is not False:
+            log = om.out.console
+            vmgr = VersionMgr(localpath=os.getcwd(), log=log)
+            msg = 'Checking if a new version is available in our code repo. ' \
+            'Please wait...'
+            vmgr.register(vmgr.ON_UPDATE, log, msg)
+            try:
+                vmgr.update(force=do_upd is True, askvalue=raw_input,
+                            print_result=True, show_log=True)
+            except SVNError, e:
+                om.out.error('An error occured while updating:\n%s' % e.args)
+            except KeyboardInterrupt:
+                pass
+
     def __initFromParent(self, parent):
         self._context = parent._context
         self._w3af = parent._w3af
@@ -124,7 +146,6 @@
     def _executePending(self):
         while (self._commands):
             curCmd, self._commands = self._commands[0], self._commands[1:]
-
             self._paste(curCmd)
             self._onEnter()
 
Index: core/ui/consoleUi/menu.py
===================================================================
--- core/ui/consoleUi/menu.py   (revision 3894)
+++ core/ui/consoleUi/menu.py   (revision 3977)
@@ -20,14 +20,12 @@
 
 '''
 
-import traceback
-
-import core.data.kb.knowledgeBase as kb        
+from core.controllers.w3afException import w3afException
 from core.ui.consoleUi.util import *
 from core.ui.consoleUi.history import *
 from core.ui.consoleUi.help import *
 import core.controllers.outputManager as om
-from core.controllers.w3afException import w3afException
+import core.data.kb.knowledgeBase as kb
 
 
 class menu:
@@ -86,7 +84,7 @@
         
         self._paramHandlers = {}
         for cmd in [c for c in dir(self) if c.startswith('_cmd_')]:
-            self._handlers[cmd[5:]] =  getattr(self, cmd)
+            self._handlers[cmd[5:]] = getattr(self, cmd)
 
         for cmd in self._handlers.keys():
             try:
@@ -219,7 +217,6 @@
         else:
             om.out.console( repr(res) )
 
-
     def _cmd_assert(self, params):
         if not len(params):
             raise w3afException('Expression is expected')
------------------------------------------------------------------------------
Special Offer-- Download ArcSight Logger for FREE (a $49 USD value)!
Finally, a world-class log management solution at an even better price-free!
Download using promo code Free_Logger_4_Dev2Dev. Offer expires 
February 28th, so secure your free ArcSight Logger TODAY! 
http://p.sf.net/sfu/arcsight-sfd2d
_______________________________________________
W3af-develop mailing list
W3af-develop@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/w3af-develop

Reply via email to