buildframework/helium/sf/python/pythoncore/lib/ccm/__init__.py
changeset 587 85df38eb4012
child 588 c7c26511138f
child 618 df88fead2976
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/buildframework/helium/sf/python/pythoncore/lib/ccm/__init__.py	Tue Apr 27 08:33:08 2010 +0300
@@ -0,0 +1,1984 @@
+#============================================================================ 
+#Name        : __init__.py 
+#Part of     : Helium 
+
+#Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+#All rights reserved.
+#This component and the accompanying materials are made available
+#under the terms of the License "Eclipse Public License v1.0"
+#which accompanies this distribution, and is available
+#at the URL "http://www.eclipse.org/legal/epl-v10.html".
+#
+#Initial Contributors:
+#Nokia Corporation - initial contribution.
+#
+#Contributors:
+#
+#Description:
+#===============================================================================
+
+""" CM/Synergy Python toolkit.
+
+"""
+
+import logging
+import netrc
+import os
+import re
+import subprocess
+import threading
+
+import fileutils
+import nokia.gscm
+import tempfile
+import socket
+
+# Uncomment this line to enable logging in this module, or configure logging elsewhere
+_logger = logging.getLogger("ccm")
+#logging.basicConfig(level=logging.DEBUG)
+
+
+VALID_OBJECT_STATES = ('working', 'checkpoint', 'public', 'prep', 'integrate', 'sqa', 'test','released')
+STATIC_OBJECT_STATES = ('integrate', 'sqa', 'test','released')
+CCM_SESSION_LOCK = os.path.join(tempfile.gettempdir(), "ccm_session.lock")
+
+def _execute(command, timeout=None):
+    """ Runs a command and returns the result data. """
+    targ = ""
+    if timeout is not None:
+        targ = "--timeout=%s" % timeout
+    process = subprocess.Popen("python -m timeout_launcher %s -- %s" % (targ, command), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
+    stdout = process.communicate()[0]
+    process.wait()
+    _logger.debug(stdout)
+    _logger.debug("Return code: %s" % process.returncode)
+    return (stdout, process.returncode)
+   
+   
+class CCMException(Exception):
+    """ Base exception that should be raised by methods of this framework. """
+    def __init__(self, reason, result = None):
+        Exception.__init__(self, reason)
+        self.result = result
+
+
+class Result(object):
+    """Class that abstracts ccm call result handling.
+    
+    Subclass it to implement a new generic output parser.
+    """
+    def __init__(self, session):
+        self._session = session
+        self.status = None
+        self._output = None
+        self._output_str = None
+    
+    def _setoutput(self, output):
+        """set output"""
+        self._output = output
+        
+    def __setoutput(self, output):
+        """ Internal function to allow overloading, you must override _setoutput.
+        """
+        # the output is automatically converted to ascii before any treatment 
+        if isinstance(output, unicode):
+            self._output_str = output.encode('ascii', 'replace')
+        else:
+            self._output_str = output.decode('ascii', 'ignore')
+        _logger.debug("output ---->")
+        for line in self._output_str.splitlines():
+            _logger.debug(line)
+        _logger.debug("<----")
+        self._setoutput(self._output_str)
+                
+    def _getoutput(self):
+        """ Returns the content of _output. """
+        return self._output
+        
+    def __str__(self):
+        """ Synergy output log. """
+        return self._output_str.encode('ascii', 'replace')
+        
+    output = property(_getoutput, __setoutput)
+
+class ResultWithError(Result):
+    """ A result class to parse output errors """
+    def __init__(self, session):
+        Result.__init__(self, session)
+        self._error = None
+        self._error_str = None    
+
+    def _seterror(self, error):
+        """set the error """
+        self._error = error
+        
+    def __seterror(self, error):
+        """ Internal function to allow overloading, you must override _seterror.
+        """
+        # the error output is automatically converted to ascii before any treatment 
+        if isinstance(error, unicode):
+            self._error_str = error.encode('ascii', 'replace')
+        else:
+            self._error_str = error.decode('ascii', 'ignore')
+        _logger.debug("error ---->")
+        for line in self._error_str.splitlines():
+            _logger.debug(line)
+        _logger.debug("<----")
+        self._seterror(self._error_str)
+                
+    def _geterror(self):
+        """ Returns the content of _output. """
+        _logger.debug("_geterror")
+        return self._error
+
+    error = property(_geterror, __seterror)
+    
+class ProjectCheckoutResult(Result):
+    """ Project checkout output parser. 
+        Sets project to the created project or None if failed.
+    """
+    def __init__(self, session, project):
+        Result.__init__(self, session)
+        self.__project = project
+        self.__result_project = None
+    
+    def _setoutput(self, output):
+        """ Parsing the output of the checkout command. """
+        self._output = output
+        for line in output.splitlines():
+            mresult = re.match(r"Saved work area options for project: '(.+)'", line, re.I)
+            #(?P<name>.+)-(?P<version>.+?)(:(?P<type>\S+):(?P<instance>\S+))?
+            if mresult != None:
+                #self.__project.name + "-" + mo.groupdict()['version'] + ":" + self.__project.type + ":" + self.__project.instance
+                self.__result_project = self._session.create(mresult.group(1))
+                _logger.debug("ProjectCheckoutResult: project: '%s'" % self.__result_project)
+                return
+
+    def __get_result_project(self):
+        """ return the checked out project. """
+        return self.__result_project
+    
+    project = property(__get_result_project)
+
+
+class ProjectPurposeResult(Result):
+    """ Parses purpose query output. """
+    def __init__(self, session):
+        Result.__init__(self, session)
+
+    def _setoutput(self, output):
+        """set output """
+        self._output = {}
+        for line in output.splitlines():
+            mresult = re.match(r"(?P<purpose>.+?)\s+(?P<member_status>\w+)\s+(?P<status>\w+)$", line)
+            if mresult != None:
+                data = mresult.groupdict()                
+                if re.match(r'^\s+Purpose\s+Member$', data['purpose'], re.I) == None:
+                    self._output[data['purpose'].strip()] = {'member_status' : data['member_status'].strip(),
+                                                  'status' : data['status'].strip()}
+
+class ConflictsResult(Result):
+    """ Parses purpose query output. """
+    def __init__(self, session):
+        Result.__init__(self, session)
+
+    def _setoutput(self, output):
+        """ set output """
+        self._output = {}
+        project = None
+                
+        for line in output.splitlines():
+            mresult = re.match(r"Project:\s*(.+)\s*$", line)
+            if mresult != None:
+                project = self._session.create(mresult.group(1))
+                self._output[project] = []
+            mresult = re.match(r"^(.*)\s+(\w+#\d+)\s+(.+)$", line)
+            if mresult != None and project != None:
+                self._output[project].append({'object': self._session.create(mresult.group(1)),
+                                              'task': self._session.create("Task %s" % mresult.group(2)),
+                                              'comment': mresult.group(3)})
+            mresult = re.match(r"^(\w+#\d+)\s+(.+)$", line)
+            if mresult != None and project != None:
+                self._output[project].append({'task': self._session.create("Task %s" % mresult.group(1)),
+                                              'comment': mresult.group(2)})
+
+
+class FinduseResult(Result):
+    """ Parses finduse query output. """
+    def __init__(self, ccm_object):
+        Result.__init__(self, ccm_object.session)
+        self.__object = ccm_object
+
+    def _setoutput(self, output):
+        """set output"""
+        self._output = []
+        for line in output.splitlines():
+            _logger.debug("FinduseResult: ---->%s<----" % line)
+            _logger.debug("FinduseResult: ---->%s-%s<----" % (self.__object.name, self.__object.version))
+            
+            # MCNaviscroll\NaviAnim-username7@MCNaviscroll-username6            
+            mresult = re.match(r"^\s*(?P<path>.+)[\\/]%s-%s@(?P<project>.+)" % (self.__object.name, self.__object.version), line, re.I)
+            if mresult != None:
+                data = mresult.groupdict()
+                _logger.debug("FinduseResult: %s" % data)               
+                project = self._session.create(data['project'])
+                self._output.append({'path' : data['path'], 'project' : project})
+        
+        
+class UpdateTemplateInformation(Result):
+    """ Parse update template information output. """
+    def __init__(self, session):
+        Result.__init__(self, session)
+    
+    def _setoutput(self, output):
+        """
+Baseline Selection Mode: Latest Baseline Projects
+Prep Allowed:            No
+Versions Matching:       *abs.50*
+Release Purposes:
+Use by Default:          Yes
+Modifiable in Database:  tr1s60
+In Use For Release:      Yes
+Folder Templates and Folders:
+- Template assigned or completed tasks for %owner for release %release
+- Template all completed tasks for release %release
+- Folder   tr1s60#4844: All completed Xuikon/Xuikon_rel_X tasks
+- Folder   tr1s60#4930: All tasks for release AppBaseDo_50        
+        """
+        self._output = {}
+        for line in output.splitlines():
+            rmo = re.match(r"^\s*(.+):\s*(.*)\s*", line)
+            if rmo != None:
+                if rmo.group(1) == "Baseline Selection Mode":
+                    self._output['baseline_selection_mode'] = rmo.group(2) 
+                elif rmo.group(1) == "Prep Allowed":
+                    self._output['prep_allowed'] = (rmo.group(2) != "No") 
+                elif rmo.group(1) == "Versions Matching":
+                    self._output['version_matching'] = rmo.group(2) 
+                elif rmo.group(1) == "Release Purposes":
+                    self._output['release_purpose'] = rmo.group(2) 
+                elif rmo.group(1) == "Use by Default":
+                    self._output['default'] = (rmo.group(2) != "No") 
+                elif rmo.group(1) == "Modifiable in Database":
+                    self._output['modifiable_in_database'] = rmo.group(2).strip()
+                elif rmo.group(1) == "In Use For Release":
+                    self._output['in_use_for_release'] = (rmo.group(2) != "No") 
+                
+
+class UpdatePropertiesRefreshResult(Result):
+    """ Parse update template refresh output. """
+    def __init__(self, session):
+        Result.__init__(self, session)
+
+    def _setoutput(self, output):
+        """set output"""
+        self._output = {'added': [], 'removed': []}
+        match_added = re.compile(r"^Added the following tasks")
+        match_removed = re.compile(r"^Removed the following tasks")
+        match_task_new = re.compile(r"^\s+(Task \S+#\d+)")        
+        section = None
+                
+        for line in output.splitlines():
+            res = match_added.match(line)
+            if res != None:
+                section = 'added'
+                continue
+            res = match_removed.match(line)
+            if res != None:
+                section = 'removed'
+                continue
+            if section is not None:
+                res = match_task_new.match(line)
+                if res != None:
+                    self._output[section].append(self._session.create(res.group(1)))
+                    continue
+
+
+class UpdateResultSimple(Result):
+    """ Parse update output. """
+    def __init__(self, session):
+        Result.__init__(self, session)
+        self._success = True
+
+    def _setoutput(self, output):
+        """set output"""
+        self._output = output
+        match_failed = re.compile(r"(Update failed)")
+        for line in output.splitlines():
+            res = match_failed.match(line)
+            if res != None:
+                self._success = False
+
+    @property
+    def successful(self):
+        """successful"""
+        return self._success
+
+class UpdateResult(UpdateResultSimple):
+    """ Parse update output. """
+    def __init__(self, session):
+        UpdateResultSimple.__init__(self, session)
+
+    def _setoutput(self, output):
+        """set output"""
+        self._output = {"tasks":[], "modifications": [], "errors": [], "warnings": []}
+        match_object_update = re.compile(r"^\s+'(.*)'\s+replaces\s+'(.*)'\s+under\s+'(.*)'\.")
+        match_object_new = re.compile(r"^\s+(?:Subproject\s+)?'(.*)'\s+is now bound under\s+'(.*)'\.")
+        match_task_new = re.compile(r"^\s+(Task \S+#\d+)")
+        match_no_candidate = re.compile(r"^\s+(.+) in project (.+) had no candidates")
+        match_update_failure = re.compile(r"^\s+Failed to use selected object\s+(.+)\s+under directory\s+(.+)\s+in project\s+(.+)\s+:\s+(.+)")
+        match_warning = re.compile(r"^Warning:(.*)")
+        match_failed = re.compile(r"(Update failed)")
+        
+        # TODO: cleanup the parsing to do that in a more efficient way.
+        for line in output.splitlines():
+            _logger.info(line)
+            res = match_object_update.match(line)
+            if res != None:
+                self._output['modifications'].append({ "new": self._session.create(res.group(1)),
+                                      "old": self._session.create(res.group(2)),
+                                      "project": self._session.create(res.group(3))})
+                continue
+            res = match_object_new.match(line)
+            if res != None:                
+                self._output['modifications'].append({ "new": self._session.create(res.group(1)),
+                                      "old": None,
+                                      "project": self._session.create(res.group(2))})
+                continue
+            res = match_task_new.match(line)
+            if res != None:                
+                self._output['tasks'].append(self._session.create(res.group(1)))
+                continue
+            res = match_no_candidate.match(line)
+            if res != None:                
+                self._output['errors'].append({'family': res.group(1),
+                                               'project': self._session.create(res.group(2)),
+                                               'comment': "had no candidates",
+                                               'line': line,})
+                continue
+            res = match_update_failure.match(line)
+            if res != None:                
+                self._output['errors'].append({'family': res.group(1),
+                                               'dir': self._session.create(res.group(2)),
+                                               'project': self._session.create(res.group(3)),
+                                               'comment': res.group(4),
+                                               'line': line,
+                                               })
+                continue
+            res = match_warning.match(line)            
+            if res != None:                
+                self._output['warnings'].append({'family': None,
+                                               'project': None,
+                                               'comment': res.group(1),
+                                               'line': line,
+                                               })
+                continue
+            res = match_failed.match(line)
+            if res != None:
+                self._success = False
+                self._output['errors'].append({'Serious': res.group(1),
+                                               })
+                continue
+                
+            
+
+class WorkAreaInfoResult(Result):
+    """ Parse work area info output. """
+    def __init__(self, session):
+        Result.__init__(self, session)
+
+    def _setoutput(self, output):
+        """ Returns a dict with the following fields:
+               * project: a ccm.Project instance
+               * maintain: a boolean
+               * copies: a boolean
+               * relative: a boolean
+               * time: a boolean
+               * translate: a boolean
+               * modify: a boolean
+               * path: a string representing the project wa path
+        """
+        self._output = None
+        for line in output.splitlines():
+            mresult = re.match(r"(?P<project>.*)\s+(?P<maintain>TRUE|FALSE)\s+(?P<copies>TRUE|FALSE)\s+(?P<relative>TRUE|FALSE)\s+(?P<time>TRUE|FALSE)\s+(?P<translate>TRUE|FALSE)\s+(?P<modify>TRUE|FALSE)\s+'(?P<path>.*)'", line)            
+            if mresult != None:
+                data = mresult.groupdict()
+                self._output = {'project': self._session.create(data['project']),
+                                'maintain' : data['maintain'] == "TRUE",
+                                'copies' : data['copies'] == "TRUE",
+                                'relative' : data['relative'] == "TRUE",
+                                'time' : data['time'] == "TRUE",
+                                'translate' : data['translate'] == "TRUE",
+                                'modify' : data['modify'] == "TRUE",
+                                'path' : data['path']
+                                }
+                return
+
+
+class CreateNewTaskResult(Result):
+    """ A result class to parse newly created tasks """
+    def __init__(self, session):
+        Result.__init__(self, session)
+
+    def _setoutput(self, output):
+        """set output"""
+        self._output = None
+        for line in output.splitlines():
+            mresult = re.match(r"Task\s+(?P<task>\S+\#\d+)\s+created\.", line)
+            if mresult != None:
+                self._output = self._session.create("Task " + mresult.groupdict()['task'])
+                return
+
+
+class AttributeNameListResult(Result):
+    """ Class that abstract ccm call result handling.
+        Subclass it to implement a new generic output parser.
+    """
+    def __init__(self, session):
+        Result.__init__(self, session)
+
+    def _setoutput(self, obj):
+        """set output"""
+        def _create(arg):
+            """create"""
+            mresult = re.match(r"^\s*(?P<name>\w+)", arg.strip())
+            if mresult != None:
+                return mresult.groupdict()['name']
+            return None
+        self._output = [_create(line) for line in obj.strip().splitlines()]
+
+
+class ObjectListResult(Result):
+    """ Parses an object list Synergy output. """
+    def __init__(self, session):
+        Result.__init__(self, session)
+
+    def _setoutput(self, obj):        
+        """set output"""
+        self._output = []
+        if re.match(r"^None|^No tasks|^Warning", obj, re.M) != None:
+            return
+        def _create(arg):
+            """create"""
+            arg = arg.strip()
+            if arg != "":
+                return self._session.create(arg)
+            return None
+        result = [_create(line) for line in obj.strip().splitlines()]
+        for result_line in result:
+            if result_line != None:
+                self._output.append(result_line)
+
+class DataMapperListResult(Result):
+    """ Parses an object list Synergy output. """
+    
+    dataconv = {'ccmobject': lambda x, y: x.create(y),
+                'string': lambda x, y: y,
+                'int': lambda x, y: int(y),
+                'boolean': lambda x, y: (y.lower() == "true")}
+
+    def __init__(self, session, separator, keywords, datamodel):
+        self._separator = separator
+        self._keywords = keywords
+        self._datamodel = datamodel
+        Result.__init__(self, session)
+
+    def format(self):
+        """format keywords"""
+        formatted_keywords = ["%s%s%s%%%s" % (self._separator, x, self._separator, x) for x in self._keywords]
+        return "".join(formatted_keywords) + self._separator
+
+    def regex(self):
+        """regular expression"""
+        regex_keywords = [r'%s%s%s(.*?)' % (self._separator, x, self._separator) for x in self._keywords]
+        regex = r''.join(regex_keywords)
+        regex = r"%s%s\s*\n" % (regex, self._separator)
+        return re.compile(regex, re.MULTILINE | re.I | re.DOTALL | re.VERBOSE | re.U)
+
+    def _setoutput(self, obj):
+        """set output"""
+        self._output = []
+        regex = self.regex()
+        _logger.debug("Regex %s" % (regex.pattern))
+        for match in regex.finditer(obj):
+            _logger.debug("Found: %s" % (match))
+            if match != None:
+                output_line = {}
+                for i in range(len(self._datamodel)):
+                    _logger.debug("Found %d: %s" % (i, match.group(i + 1)))
+                    model = self._datamodel[i]
+                    output_line[self._keywords[i]] = self.dataconv[model](self._session, match.group(i + 1))
+                    i += 1
+                self._output.append(output_line)
+                
+
+class FolderCopyResult(Result):
+    """ Parses a folder copy result """
+    def __init__(self, session):
+        Result.__init__(self, session)
+
+    def _setoutput(self, output):
+        """set output"""
+        self._output = None
+        for line in output.splitlines():
+            match_output = re.match(r"appended to", line)
+            if match_output != None:
+                self._output = self._session.create(line)
+                return
+
+CHECKOUT_LOG_RULES = [[r'^Derive failed for', logging.ERROR],
+                      [r'^Serious:', logging.ERROR],
+                      [r'^Warning: .* failed.', logging.ERROR],
+                      [r'^Invalid work area', logging.ERROR],
+                      [r'^WARNING:', logging.WARNING],
+                      [r'^Warning:', logging.WARNING],]
+
+
+UPDATE_LOG_RULES = [[r'^Update failed.', logging.ERROR],
+                    [r'^Serious:', logging.ERROR],
+                    [r'^\s+Failed to', logging.ERROR],
+                    [r'^\d+ failures to', logging.ERROR],
+                    [r"^Warning: This work area '.+' cannot be reused", logging.ERROR],
+                    [r'^Rebind of .* failed', logging.ERROR],
+                    [r'^Warning: .* failed.', logging.ERROR],
+                    [r'^Skipping \'.*\'\.  You do not have permission to modify this project.', logging.ERROR],
+                    [r'^Work area conflict exists for file', logging.ERROR],
+                    [r'^Warning:  No candidates found for directory entry', logging.ERROR],
+                    [r'^WARNING:', logging.WARNING],
+                    [r'^Warning:', logging.WARNING],]
+
+CONFLICTS_LOG_RULES = [[r'^\w+#\d+\s+Implicit', logging.WARNING],
+                       [r'^(.*)\s+(\w+#\d+)\s+(.+)', logging.WARNING],
+                       [r'.*Explicitly specified but not included', logging.WARNING],]
+
+SYNC_LOG_RULES = [[r'^\s+0\s+Conflict\(s\) for project', logging.INFO],
+                  [r'^\s+\d+\s+Conflict\(s\) for project', logging.ERROR],
+                  [r'^Project \'.*\' does not maintain a workarea.', logging.ERROR],
+                  [r'^Work area conflict exists for file', logging.ERROR],
+                  [r'^Warning: Conflicts detected during synchronization. Check your logs.', logging.ERROR],
+                  [r'^Warning:', logging.WARNING],]
+
+def log_result(result, rules, logger=None):
+    """ Rules it a list of tuple defining a regular expression and an log level. """
+    if logger is None:
+        logger = _logger
+    crules = []
+    if rules is not None:
+        for rule in rules:
+            crules.append([re.compile(rule[0]), rule[1]])
+                
+    for line in str(result).splitlines():
+        for rule in crules:
+            if rule[0].match(line) != None:
+                logger.log(rule[1], line)
+                break
+        else:
+            logger.info(line)
+    
+class AbstractSession(object):
+    """An abstract Synergy session.
+
+    Must be overridden to implement either a single session or
+    multiple session handling.
+    """
+    def __init__(self, username, engine, dbpath, ccm_addr):
+        self.username = username
+        self.engine = engine
+        self.dbpath = dbpath
+        self._session_addr = ccm_addr
+        # internal object list
+        self.__ccm_objects = {}
+    
+    def addr(self):
+        """ Returns the Synergy session id."""
+        return self._session_addr
+    
+    def database(self):
+        """database"""
+        _logger.debug("AbstractSession: database")
+        self.__find_dbpath()
+        _logger.debug("AbstractSession: database: %s" % self.dbpath)
+        return os.path.basename(self.dbpath)
+    
+    def __find_dbpath(self):
+        """ retrieve the database path from current session status. """
+        _logger.debug("AbstractSession: __find_dbpath")
+        if (self.dbpath != None):            
+            return
+        result = self.execute("status")
+        for match in re.finditer(r'(?:(?:Graphical)|(?:Command)) Interface\s+@\s+(?P<ccmaddr>\w+:\d+(?:\:\d+\.\d+\.\d+\.\d+)+)(?P<current_session>\s+\(current\s+session\))?\s*\nDatabase:\s*(?P<dbpath>\S+)', result.output, re.M | re.I):
+            dictionary = match.groupdict()
+            if (dictionary['current_session'] != None):
+                _logger.debug("AbstractSession: __find_dbpath: Found dbpath: %s" % dictionary['dbpath'])
+                self.dbpath = dictionary['dbpath']
+        assert self.dbpath != None
+    
+    def execute(self, _, result=None):
+        """ Abstract function that should implement the execution of ccm command
+            line call.
+        """
+        return result
+
+    def create(self, fpn):
+        """ Object factory, this is the toolkit entry point to create objects from
+            four part names. Objects are stored into a dictionary, so you have
+            only one wrapper per synergy object.
+        """
+        result = re.search(r"^(?P<project>.+)-(?P<version>[^:]+?)$", fpn)
+        if result != None:
+            matches = result.groupdict()
+            fpn = "%s-%s:project:%s#1" % (matches['project'], matches['version'], self.database())
+        _logger.debug("session.create('%s')" % fpn)
+        ofpn = FourPartName(fpn)
+        if not self.__ccm_objects.has_key(str(fpn)):
+            obj = None
+            if ofpn.type == 'project':
+                obj = Project(self, fpn)
+            elif ofpn.type == 'dir':
+                obj = Dir(self, fpn)
+            elif ofpn.type == 'task':
+                obj = Task(self, fpn)
+            elif ofpn.type == 'folder':
+                obj = Folder(self, fpn)
+            elif ofpn.type == 'releasedef':
+                obj = Releasedef(self, fpn)
+            else:
+                obj = File(self, fpn)
+            self.__ccm_objects[str(fpn)] = obj
+        return self.__ccm_objects[str(fpn)]
+
+    def get_workarea_info(self, dir_):
+        """ Return a dictionary containing workarea info from directory dir.
+        """
+        if (not os.path.exists(dir_)):
+            raise CCMException("Error retrieving work_area info for the directory '%s' (doesn't exists)" % dir_)
+        path = os.path.abspath(os.path.curdir)
+        path_ccmwaid = os.path.join(dir_,"_ccmwaid.inf")
+        if(not os.path.exists(path_ccmwaid)):
+            raise CCMException("No work area in '%s'" % dir_)
+        os.chdir(dir_)
+        result = self.execute("wa -show", WorkAreaInfoResult(self))
+        os.chdir(path)
+        if result.output == None:
+            raise CCMException("Error retrieving work_area info for the directory '%s'" % dir_)
+        return result.output
+
+    def _get_role(self):
+        """get CCM role"""
+        result = self.execute("set role")
+        return result.output.strip()
+    
+    def _set_role_internal(self, role):
+        """ method to be override by child class else property accession is not working properly. """
+        if  role == None or len(role) == 0:
+            raise CCMException("You must provide a role.")
+        result = self.execute("set role %s" % role)
+        if re.match(r'^Warning:', result.output, re.M) != None:
+            raise CCMException("Error switching to role %s: %s" %(role, result.output.strip()))
+
+    def _set_role(self, role):
+        """set CCM role"""
+        self._set_role_internal(role)
+        
+    role = property(fget=_get_role, fset=_set_role)
+    
+    def _get_home(self):
+        """get home"""
+        result = self.execute("set Home")
+        return result.output.strip()
+        
+    def _set_home(self, home):
+        """set home"""
+        if len(home) == 0 or home == None:
+            raise CCMException("You must provide a home.")
+        result = self.execute("set Home %s" % home)
+        if re.match(r'^Warning:', result.output, re.M) != None:
+            raise CCMException("Error switching to Home %s: %s" %(home, result.output.strip()))
+    
+    home = property(_get_home, _set_home)
+    
+    def close(self):
+        """close does nothing"""
+        pass
+    
+    def __str__(self):
+        self.__find_dbpath()
+        return self._session_addr + ':' + self.dbpath
+        
+    def __repr__(self):
+        return self.__str__()
+    
+    def __del__(self):
+        self.close()
+
+    def purposes(self, role=None):
+        """ Returns available purposes. """
+        args = ""
+        if role != None:
+            args = "-role \"%s\"" % role
+        result = self.execute("project_purpose -show %s" % args, ProjectPurposeResult(self))
+        return result.output        
+
+class Session(AbstractSession):
+    """A Synergy session.
+    """
+    def __init__(self, username, engine, dbpath, ccm_addr, close_on_exit=True):
+        AbstractSession.__init__(self, username, engine, dbpath, ccm_addr)
+        self._execute_lock = threading.Lock()
+        self.close_on_exit = close_on_exit
+
+    @staticmethod
+    def start(username, password, engine, dbpath, timeout=300):
+        """start the session"""
+        if username == None:
+            raise CCMException('username is not valid')
+        if password == None:
+            raise CCMException('password is not valid')
+        if CCM_BIN == None:
+            raise CCMException("Could not find CM/Synergy executable in the path.")
+        command = "%s start -m -q -nogui -n %s -pw %s -h %s -d %s" % \
+                    (CCM_BIN, username, password, engine, dbpath)
+        _logger.debug('Starting new session:' + command.replace(password, "***"))
+        (result, status) = _execute(command, timeout=timeout)
+        if status != 0:
+            raise Exception("Error creating a session: result:\n%s\nCommand: %s" % (result, command.replace(password, "***")))
+        session_addr = result.strip()
+        _logger.debug(session_addr)
+        if not re.match(r'[a-zA-Z0-9_\-.]+:\d+:\d+\.\d+\.\d+\.\d+(:\d+\.\d+\.\d+\.\d+)?', session_addr):
+            raise Exception("Error creating a session: result:\n%s" % result)
+        return Session(username, engine, dbpath, session_addr)        
+            
+    def execute(self, cmdline, result=None):
+        """ Executes a Synergy CLI operation. """
+        if self._session_addr == None:
+            raise CCMException("No Synergy session running")
+        if CCM_BIN == None:
+            raise CCMException("Could not find CM/Synergy executable in the path.")
+        self._execute_lock.acquire()
+        output = ""
+        error = ""
+        try:
+            if result == None:
+                result = Result(self)
+            if os.sep == '\\':
+                command = "set CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " %s" % cmdline
+            else:
+                command = "export CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " %s" % cmdline
+            _logger.debug('Execute > ' + command)
+
+            if hasattr(result, 'error'):
+                process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+                output = process.stdout.read()
+                error = process.stderr.read()
+                result.status = process.returncode
+            else:
+                process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+                output = process.stdout.read()
+                result.status = process.returncode
+        finally:
+            self._execute_lock.release()
+        result.output = output.strip()
+        if hasattr(result, 'error'):
+            result.error = error.strip()
+        return result
+
+    def close(self):
+        """ Closes this Synergy session if it was not previously running anyway. """
+        _logger.debug("Closing session %s" % self._session_addr)
+        if self._session_addr != None and self.close_on_exit:
+            _logger.debug("Closing session %s" % self._session_addr)
+            self._execute_lock.acquire()
+            if os.sep == '\\':
+                command = "set CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " stop"
+            else:
+                command = "export CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " stop"
+            _logger.debug('Execute > ' + command)
+            pipe = os.popen(command)
+            pipe.close()
+            self._session_addr = None
+            self._execute_lock.release()
+        elif self._session_addr != None and not self.close_on_exit:
+            _logger.debug("Keeping session %s alive." % self._session_addr)
+
+
+class SessionPool(AbstractSession):
+    """ Session that transparently handled several subsession, to easily enable
+        multithreaded application.
+    """
+    def __init__(self, username, password, engine, dbpath, database=None, size=4, opener=None):
+        AbstractSession.__init__(self, username, engine, dbpath, None)
+        self._opener = opener
+        if self._opener is None:
+            self._opener = open_session
+        self._free_sessions = []
+        self._used_sessions = []
+        self._thread_sessions = {}
+        self._pool_lock = threading.Condition()
+        self._lock_pool = False
+        self.__password = password
+        self.__database = database        
+        self.size = size
+    
+    def _set_size(self, size):
+        """ Set the pool size """ 
+        self._pool_lock.acquire()
+        poolsize = len(self._free_sessions) + len(self._used_sessions)
+        if  poolsize > size:
+            to_be_remove = poolsize - size
+            self._lock_pool = True
+            while len(self._free_sessions) < to_be_remove:
+                self._pool_lock.wait()            
+            for _ in range(to_be_remove):
+                self._free_sessions.pop().close()
+            self._lock_pool = False
+        else: 
+            for _ in range(size - poolsize):
+                self._free_sessions.append(self._opener(self.username, self.__password, self.engine, self.dbpath, self.__database, False))
+        self._pool_lock.release()
+
+    def _get_size(self):
+        """get pool size"""
+        self._pool_lock.acquire()
+        poolsize = len(self._free_sessions) + len(self._used_sessions)
+        self._pool_lock.release()
+        return poolsize
+
+    size = property (_get_size, _set_size)
+    
+    def execute(self, cmdline, result=None):
+        """ Executing a ccm command on a free session. """        
+        _logger.debug("SessionPool:execute: %s %s" % (cmdline, type(result)))
+        
+        # waiting for a free session
+        self._pool_lock.acquire()        
+        
+        # check for recursion, in that case reallocate the same session,
+        if threading.currentThread() in self._thread_sessions:
+            _logger.debug("Same thread, reusing allocation session.")
+            # release the pool and reuse associated session
+            self._pool_lock.release()
+            return self._thread_sessions[threading.currentThread()].execute(cmdline, result)
+
+        while len(self._free_sessions)==0 or self._lock_pool:
+            self._pool_lock.wait()
+        session = self._free_sessions.pop(0)
+        self._used_sessions.append(session)
+        self._thread_sessions[threading.currentThread()] = session
+        self._pool_lock.release()
+        
+        # running command
+        try:
+            result = session.execute(cmdline, result)
+        finally:
+            # we can now release the session - anyway
+            self._pool_lock.acquire()
+            self._thread_sessions.pop(threading.currentThread())                
+            self._used_sessions.remove(session)
+            self._free_sessions.append(session)
+            self._pool_lock.notifyAll()
+            self._pool_lock.release()
+        return result
+
+    def close(self):
+        """ Closing all subsessions. """
+        _logger.debug("Closing session pool sub-sessions")
+        self._lock_pool = True
+        self._pool_lock.acquire()
+        while len(self._used_sessions) > 0:
+            _logger.debug("Waiting to free used sessions.")
+            _logger.debug("Waiting to free used sessions. %s %s" % (len(self._used_sessions), len(self._free_sessions)))            
+            _logger.debug(self._used_sessions)
+            _logger.debug(self._free_sessions)            
+            self._pool_lock.wait()
+        _logger.debug("Closing all free session from the pool.")
+        while len(self._free_sessions) > 0:
+            self._free_sessions.pop().close()
+        self._lock_pool = False
+        self._pool_lock.notifyAll()
+        self._pool_lock.release()
+    
+    def _set_role_internal(self, role):
+        """ Set role on all subsessions. """
+        self._lock_pool = True
+        self._pool_lock.acquire()
+        while len(self._used_sessions)!=0:
+            self._pool_lock.wait()
+            
+        try:
+            for session in self._free_sessions:
+                session.role = session._set_role(role)
+        finally:                
+            self._lock_pool = False
+            self._pool_lock.notifyAll()
+            self._pool_lock.release()
+
+
+class Query(object):
+    """ This object wrap a synergy query, it takes a query as input as well as the
+    attribute you want as output, and get them translated using the model configuration.
+    e.g 
+    Query(session, "type='task' and release='test/next'", ['objectname', 'task_synopsis'], ['ccmobject', 'string'])
+    
+    This will return a list of hash: [{'objectname': Task(xxx), 'task_synopsis': 'xxx'}, ...]
+    """
+    
+    def __init__(self, session, query, keywords, model, cmd="query"):
+        """ Initialize a Synergy query."""
+        self._session = session
+        self._query = query
+        self._keywords = keywords
+        self._model = model
+        self._cmd = cmd
+        
+    def execute(self):
+        """ Executing the query on the database. """
+        mapper = DataMapperListResult(self._session, '@@@', self._keywords, self._model)        
+        query = "%s %s -u -f \"%s\"" % (self._cmd, self._query, mapper.format())
+        return self._session.execute(query, mapper)
+        
+    
+
+class InvalidFourPartNameException(CCMException):
+    """ Badly formed Synergy four-part name. """
+    def __init__(self, fpn = ""):
+        CCMException.__init__(self, fpn)
+
+
+class FourPartName(object):
+    """ This class handle four part name parsing and validation.
+    """
+
+    def __init__(self, ifpn):
+        """ Create a FourPartName object based on a ifpn string.
+        
+        The string have to match the following patterns:
+        - name-version:type:instance
+        - name:version:releasedef:instance
+        - Task database#id
+        - Folder database#id
+        
+        Anything else is considered as old release string format.
+            
+        """
+        _logger.debug("FourPartName: '%s'", ifpn)
+        fpn = FourPartName.convert(ifpn)
+        result = re.search(r"^(?P<name>.+)-(?P<version>.+?):(?P<type>\S+):(?P<instance>\S+)$", fpn)
+        if result == None:
+            result = re.search(r"^(?P<name>.+):(?P<version>.+?):(?P<type>releasedef):(?P<instance>\S+)$", fpn)            
+            if result == None:
+                raise InvalidFourPartNameException(fpn)
+        # set all attributes
+        self._name = result.groupdict()['name']
+        self._version = result.groupdict()['version']
+        self._type = result.groupdict()['type']
+        self._instance = result.groupdict()['instance']    
+
+    def __getname(self):
+        """ Returns the name of the object. """
+        return self._name
+    
+    def __getversion(self):
+        """ Returns the version of the object. """
+        return self._version
+    
+    def __gettype(self):
+        """ Returns the type of the object. """
+        return self._type
+    
+    def __getinstance(self):
+        """ Returns the instance of the object. """
+        return self._instance
+    
+    def __getobjectname(self):
+        """ Returns the objectname of the object. """
+        if (self.type == 'releasedef'):
+            return "%s:%s:%s:%s" % (self.name, self.version, self.type, self.instance)
+        return "%s-%s:%s:%s" % (self.name, self.version, self.type, self.instance)
+    
+    def __str__(self):
+        """ Returns the string representation of the object. """
+        return self.objectname
+    
+    def __repr__(self):
+        """ Returns the string representation of the python object. """
+        if (self.type == 'releasedef'):
+            return "<%s:%s:%s:%s>" % (self.name, self.version, self.type, self.instance)
+        return "<%s-%s:%s:%s>" % (self.name, self.version, self.type, self.instance)
+    
+    def is_same_family(self, ccmobject):
+        """ Returns True if the ccmobject is part of the same family (=same name, type and instance) as self. """
+        assert isinstance(ccmobject, FourPartName)
+        return (self.name == ccmobject.name and self.type == ccmobject.type and self.instance == ccmobject.instance)
+    
+    def __getfamily(self):
+        """get family"""
+        return "%s:%s:%s" % (self.name, self.type, self.instance)
+    
+    def __eq__(self, ccmobject):
+        """ Returns True if object four parts name are identical. """
+        if ccmobject == None:
+            return False
+        assert isinstance(ccmobject, FourPartName)
+        return (self.name == ccmobject.name and self.version == ccmobject.version and self.type == ccmobject.type and self.instance == ccmobject.instance)
+    
+    def __ne__(self, ccmobject):
+        """ Returns True if object four parts name are different. """
+        if ccmobject == None:
+            return True
+        assert isinstance(ccmobject, FourPartName)
+        return (self.name != ccmobject.name or self.version != ccmobject.version or self.type != ccmobject.type or self.instance != ccmobject.instance)
+    
+    @staticmethod
+    def is_valid(fpn):
+        """ Check if a given string represents a valid four part name.
+        """        
+        return (re.match(r"^(.+)-(.+?):(\S+):(\S+)|(.+):(.+?):releasedef:(\S+)$", fpn) != None)
+    
+    @staticmethod
+    def convert(fpn):
+        """ Update a CCM output string to a valid four part name. This is due to the inconsistent
+             output of CM/Synergy CLI.
+        """
+        fpn = fpn.strip()
+        if FourPartName.is_valid(fpn):
+            return fpn
+        result = re.search(r"^(?P<type>Task|Folder)\s+(?P<instance>\w+)#(?P<id>\d+)$", fpn)
+        if result != None:
+            matches = result.groupdict()
+            if matches["type"] == "Task":
+                return "task%s-1:task:%s" % (matches["id"], matches["instance"])
+            elif matches["type"] == "Folder":
+                return "%s-1:folder:%s" % (matches['id'], matches['instance'])
+        else:
+            result = re.search(r"^(?P<project>\S+)/(?P<version>\S+)$", fpn)
+            if result != None:
+                matches = result.groupdict()
+                return "%s:%s:releasedef:1" % (matches['project'], matches['version'])        
+            else:
+                # Check the name doesn't contains any of the following character: " :-"
+                result = re.search(r"^[^\s^:^-]+$", fpn)
+                if result != None:
+                    return "none:%s:releasedef:1" % (fpn)
+        raise InvalidFourPartNameException(fpn)
+
+    name = property (__getname)
+    version = property (__getversion)
+    type = property (__gettype)
+    instance = property (__getinstance)
+    objectname = property (__getobjectname)
+    family = property(__getfamily)
+                
+                
+class CCMObject(FourPartName):
+    """ Base class for any Synergy object. """
+               
+    def __init__(self, session, fpn):
+        """initialisation"""
+        FourPartName.__init__(self, fpn)
+        self._session = session
+    
+    def _getsession(self):
+        """get session"""
+        return self._session
+    
+    session = property(_getsession)
+    
+    def exists(self):
+        """ Check if an the object exists in the database. """
+        return (len(self._session.execute("query \"name='%s' and version='%s' and type='%s' and instance='%s'\" -u -f \"%%objectname\"" % (self.name, self.version, self.type, self.instance), ObjectListResult(self._session)).output) == 1)
+    
+    def __setitem__(self, name, value):
+        project = ""
+        if self.type == 'project':
+            project = "-p"
+        if value.endswith("\\"):
+            value += "\\"
+        result = self._session.execute("attribute -modify \"%s\" -v \"%s\" %s \"%s\"" % (name, value, project, self))
+        if result.status != 0 and result.status != None:
+            raise CCMException("Error modifying '%s' attribute. Result: '%s'" % (name, result.output), result)
+        
+    def __getitem__(self, name):
+        """ Provides access to Synergy object attributes through the dictionary
+        item interface.
+        """
+        result = self._session.execute("query \"name='%s' and version='%s' and type='%s' and instance='%s'\" -u -f \"%%%s\"" % (self.name, self.version, self.type, self.instance, name), ResultWithError(self._session))
+        if result.status != 0 and result.status != None:
+            raise CCMException("Error retrieving '%s' attribute. Result: '%s'" % (name, result.output), result)
+        if len(result.error.strip()) > 0:
+            raise CCMException("Error retrieving '%s' attribute. Reason: '%s'" % (name, result.error), result)
+        if result.output.strip() == "<void>":
+            return None
+        return result.output.strip()
+    
+    def create_attribute(self, name, type_, value=None):
+        """create attribute"""
+        if name in self.keys():
+            raise CCMException("Attribute '%s' already exist." % (name))
+        args = ""
+        proj_arg = ""
+        if value != None:
+            args += " -value \"%s\"" % value
+        if self.type == "project":
+            proj_arg = "-p"
+        result = self._session.execute("attribute -create \"%s\" -type \"%s\" %s %s \"%s\"" % (name, type_, args, proj_arg, self.objectname))
+        if result.status != 0 and result.status != None:
+            raise CCMException("Error creating '%s' attribute. Result: '%s'" % (name, result.output), result)
+        
+    def keys(self):
+        """ The list of supported Synergy attributes. """
+        result = self._session.execute("attribute -la \"%s\"" % self, AttributeNameListResult(self._session))
+        return result.output
+    
+    def is_predecessor_of(self, obj):
+        """is predecessor of returns boolean"""
+        result = self._session.execute("query \"is_predecessor_of('%s') and name='%s'and version='%s'and type='%s'and instance='%s'\" -u -f \"%%objectname\"" % (obj, self.name, self.version, self.type, self.instance), ObjectListResult(self._session))
+        if len(result.output):
+            return True
+        return False
+        
+    def predecessors(self):
+        """predecessors"""
+        result = self._session.execute("query \"is_predecessor_of('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
+        return result.output
+
+    def successors(self):
+        """successors"""
+        result = self._session.execute("query \"is_successor_of('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
+        return result.output
+
+    def is_recursive_predecessor_of(self, obj):
+        """is_recursive_predecessor_of returns boolean"""
+        result = self._session.execute("query \"has_predecessor('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
+        for sesh in result.output:
+            if sesh == obj:
+                return True
+        for sesh in result.output:
+            if sesh.is_recursive_predecessor_of(obj):
+                return True
+        return False
+
+    def is_recursive_predecessor_of_fast(self, obj):
+        """ Fast implementation of the recursive is_predecessor_of method. """
+        input_objects = [self]
+        while len(input_objects) > 0:
+            query = " or ".join(["has_predecessor('%s')" % x for x in input_objects])
+            result = self._session.execute("query \"query\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
+            for sesh in result.output:
+                if sesh == obj:
+                    return True
+        return False
+
+    def is_recursive_sucessor_of(self, obj):
+        """is_recursive_sucessor_of returns boolean"""
+        result = self._session.execute("query \"has_successor('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
+        for sesh in result.output:
+            if sesh == obj:
+                return True
+        for sesh in result.output:
+            if sesh.is_recursive_sucessor_of(obj):
+                return True
+        return False
+
+    def is_recursive_successor_of_fast(self, obj):
+        """ Fast implementation of the recursive is_successor_of method. """
+        input_objects = [self]
+        while len(input_objects) > 0:
+            query = " or ".join(["has_successor('%s')" % x for x in input_objects])
+            result = self._session.execute("query \"query\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
+            for sesh in result.output:
+                if sesh == obj:
+                    return True
+        return False
+    
+    def relate(self, ccm_object):
+        """relate"""
+        result = self._session.execute("relate -name successor -from \"%s\" -to \"%s\"" % self, ccm_object, Result(self._session))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error relating objects %s to %s\n%s" % (self, ccm_object, result.output))
+        
+    def finduse(self):
+        """ Tries to find where an object is used. """
+        result = self._session.execute("finduse \"%s\"" % self, FinduseResult(self))
+        return result.output
+    
+    def delete(self, recurse=False):
+        """ Delete a synergy project. """
+        args = ""
+        if recurse:
+            args = args + " -recurse"
+        parg = ""
+        if self.type == "project":
+            parg = "-project"
+        result = self._session.execute("delete %s %s \"%s\"" % (args, parg, self))
+        if result.status != 0 and result.status != None:
+            raise CCMException("An error occurred while deleting object %s (error status: %s)\n%s" % (self, result.status, result.output), result)
+        return result
+
+        
+class File(CCMObject):
+    """ Wrapper for any Synergy file object """
+    
+    def __init__(self, session, fpn):
+        CCMObject.__init__(self, session, fpn)
+    
+    def content(self):
+        """content"""
+        result = self._session.execute("cat \"%s\"" % self)
+        return result.output
+    
+    def to_file(self, path):
+        """content to file"""
+        if os.path.exists(path):
+            _logger.error("Error file %s already exists" % path)
+        if not os.path.exists(os.path.dirname(path)):
+            os.makedirs(os.path.dirname(path))
+        # Content to file        
+        result = self._session.execute("cat \"%s\" > \"%s\"" % (self, os.path.normpath(path)))
+        if result.status != 0 and result.status != None:
+            raise CCMException("Error retrieving content from object %s in %s (error status: %s)\n%s" % (self, path, result.status, result.output), result)
+    
+    def merge(self, ccm_object, task):
+        """merge"""
+        assert ccm_object != None, "object must be defined."
+        assert task != None, "task must be defined."
+        assert task.type == "task", "task parameter must be of 'task' type."
+        result = self._session.execute("merge -task %s \"%s\" \"%s\"" % (task['displayname'], self, ccm_object))
+        
+        validity = 0
+        for line in result.output.splitlines():
+            if re.match(r"Merge Source completed successfully\.", line):
+                validity = 2
+            elif re.match(r"Warning: Merge Source warning. \(overlaps during merge\)\.", line):
+                validity = 1
+            else:                
+                result = re.match(r"Associated object\s+(?P<object>.+)\s+with task", line)
+                if result != None:
+                    return (self._session.create(result.groupdict()['object']), validity)
+                    
+        raise CCMException("Error during merge operation.\n" + result.output, result)
+
+    def checkin(self, state, comment=None):
+        """checkin"""
+        if comment != None:
+            comment = "-c \"%s\"" % comment
+        else:
+            comment = "-nc"
+        result = self._session.execute("checkin -s \"%s\" %s \"%s\" " % (state, comment, self))
+        for line in result.output.splitlines():
+            _logger.debug(line)
+            _logger.debug(r"Checked\s+in\s+'.+'\s+to\s+'%s'" % state)
+            if re.match(r"Checked\s+in\s+'.+'\s+to\s+'%s'" % state, line) != None:
+                return
+        raise CCMException("Error checking in object %s,\n%s" % (self, result.output), result)
+        
+
+class Project(CCMObject):
+    """ Wrapper class for Synergy project object. """
+    
+    def __init__(self, session, fpn):
+        CCMObject.__init__(self, session, fpn)
+        self._release = None
+        self._baseline = None
+
+    def _gettasks(self):
+        """gettasks"""
+        result = self._session.execute("rp -show tasks \"%s\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
+        return result.output
+
+    def add_task(self, task):
+        """ Add a task to the update properties. """
+        result = self._session.execute("up -add -task %s \"%s\"" % (task['displayname'], self.objectname))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error adding task %s to project '%s'\n%s" % (task, self, result.output))
+        
+    def remove_task(self, task):
+        """ Remove a task to the update properties. """
+        result = self._session.execute("up -remove -task %s \"%s\"" % (task['displayname'], self.objectname))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error removing task %s from project '%s'\n%s" % (task, self, result.output))
+
+    def add_folder(self, folder):
+        """ Add a folder to the update properties. """
+        result = self._session.execute("up -add -folder %s \"%s\"" % (folder['displayname'], self.objectname))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error adding folder %s to project '%s'\n%s" % (folder, self, result.output))
+        
+    def remove_folder(self, folder):
+        """ Remove a folder to the update properties. """
+        result = self._session.execute("up -remove -folder %s \"%s\"" % (folder['displayname'], self.objectname))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error removing folder %s to project '%s'\n%s" % (folder, self, result.output))
+    
+    def _getfolders(self):
+        """ Wrapper method to return the folder list from the update properties - please use the folders attribute to access it. """
+        result = self._session.execute("up -show folders \"%s\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
+        return result.output
+        
+    def _getsubprojects(self):
+        """ Wrapper method to return the subprojects list - please use the subprojects attribute to access it. """
+        result = self._session.execute("query -t project \"recursive_is_member_of('%s', none)\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
+        return result.output
+    
+    def get_members(self, recursive=False, **kargs):
+        """get members"""
+        query = "is_member_of('%s')" % self.objectname
+        if recursive:
+            query = "recursive_is_member_of('%s', none)" % self.objectname
+        for key in kargs.keys():
+            query += " and %s='%s'" % (key, kargs[key])
+        result = self._session.execute("query \"%s\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
+        return result.output
+        
+    def _getrelease(self):
+        """ Get the release of the current object. Returns a Releasedef object. """
+        self._release = Releasedef(self._session, self['release'])
+        return self._release
+
+    def _setrelease(self, release):
+        """ Set the release of the current object. """
+        self['release'] = release['displayname']
+    
+    def refresh(self):
+        """ Refresh project update properties. """
+        result = self._session.execute("up -refresh \"%s\"" % self.objectname, UpdatePropertiesRefreshResult(self._session))
+        return result.output
+    
+    def _getbaseline(self):
+        """ Get the baseline of the current project. """
+        if self._baseline == None:
+            result = self._session.execute("up -show baseline_project \"%s\" -f \"%%displayname\" -u" % self.objectname)
+            if result.output.strip().endswith('does not have a baseline project.'):
+                return None
+            self._baseline = self._session.create(result.output)
+        _logger.debug('baseline: %s' % self._baseline)
+        return self._baseline
+    
+    def set_baseline(self, baseline, recurse=False):
+        """ Set project baseline. raise a CCMException in case or error. """
+        args = ""
+        if recurse:
+            args += " -r"
+        self._baseline = None
+        result = self._session.execute("up -mb \"%s\" %s \"%s\"" % (baseline, args, self.objectname))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error setting basline of project '%s'\n%s" % (self.objectname, result.output))
+
+    def set_update_method(self, name, recurse = False):
+        """ Set the update method for the project (and subproject if recurse is True). """
+        assert name != None, "name must not be None."
+        assert len(name) > 0, "name must not be an empty string."
+        args = "-ru %s" % name
+        if recurse:
+            args += " -r"
+        result = self._session.execute("up %s \"%s\"" % (args, self))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error setting reconfigure properties to %s for project '%s'\nStatus: %s\n%s" % (name, self.objectname, result.status, result.output))
+   
+    def apply_update_properties(self, baseline = True, tasks_and_folders = True, recurse=True):
+        """ Apply update properties to subprojects. """
+        args = ""
+        if not baseline:
+            args += "-no_baseline"
+        if not tasks_and_folders:
+            args += " -no_tasks_and_folders"
+        if recurse:
+            args += " -apply_to_subprojs"
+        result = self._session.execute("rp %s \"%s\"" % (args, self.objectname))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error applying update properties to subprojects for '%s'\n%s" % (self.objectname, result.output))
+    
+    def root_dir(self):
+        """ Return the directory attached to a project. """
+        result = self._session.execute("query \"is_child_of('%s','%s')\" -u -f \"%%objectname\"" % (self.objectname, self.objectname), ObjectListResult(self._session))
+        return result.output[0]
+    
+    def snapshot(self, targetdir, recursive=False):
+        """ Take a snapshot of the project. """
+        assert targetdir != None, "targetdir must be defined."
+        if recursive:
+            recursive = "-recurse"
+        else:
+            recursive = ""
+        result = self._session.execute("wa_snapshot -path \"%s\"  %s \"%s\"" % (os.path.normpath(targetdir), recursive, self.objectname))
+        for line in result.output.splitlines():
+            if re.match(r"^Creation of snapshot work area complete.|Copying to file system complete\.\s*$", line):
+                return result.output
+        raise CCMException("Error creation snapshot of %s,\n%s" % (self.objectname, result.output), result)
+    
+    def checkout(self, release, version=None, purpose=None, subprojects=True):
+        """ Create a checkout of this project. 
+        
+        This will only checkout the project in Synergy. It does not create a work area.
+        
+        :param release: The Synergy release tag to use.
+        :param version: The new version to use for the project. This is applied to all subprojects.
+        :param purpose: The purpose of the checkout. Determines automatically the role from the purpose
+         and switch it automatically (Could be any role from the DB).
+        """    
+        assert release != None, "Release object must be defined."
+        if not release.exists():
+            raise CCMException("Release '%s' must exist in the database." % release)
+            
+        args = ''
+        if version != None:
+            args += '-to "%s"' % version
+        role = None
+        if purpose:
+            #save current role before changing
+            role = self._session.role
+
+            self._session.role = get_role_for_purpose(self._session, purpose)
+            
+            args += " -purpose \"%s\"" % purpose
+        if subprojects:
+            args += " -subprojects"
+        result = self._session.execute("checkout -project \"%s\" -release \"%s\" -no_wa %s" \
+                                  % (self, release['displayname'], args), ProjectCheckoutResult(self._session, self.objectname))
+        if not role is  None:
+            self._session.role = role
+        if result.project == None:
+            raise CCMException("Error checking out project %s,\n%s" % (self.objectname, result.output), result)
+        return result
+    
+    def work_area(self, maintain, recursive=None, relative=None, path=None, pst=None, wat=False):
+        """ Configure the work area. This allow to enable it or disable it, set the path, recursion... """
+        args = ""
+        if maintain:
+            args += "-wa"
+        else:
+            args += "-nwa"
+        # path
+        if path != None:
+            args += " -path \"%s\"" % path        
+        # pst
+        if pst != None:
+            args += " -pst \"%s\"" % pst
+        # relative
+        if relative != None and relative:
+            args += " -relative"
+        elif relative != None and not relative:
+            args += " -not_relative"
+        # recursive
+        if recursive != None and recursive:
+            args += " -recurse"
+        elif recursive != None and not recursive:
+            args += " -no_recurse"        
+        #wat            
+        if wat:
+            args += " -wat"
+        result = self._session.execute("work_area -project \"%s\" %s" \
+                                  % (self.objectname, args), Result(self._session))
+        return result.output
+        
+    def update(self, recurse=True, replaceprojects=True, keepgoing=False, result=None):
+        """ Update the project based on its reconfigure properties. """
+        args = ""
+        if recurse:
+            args += " -r "
+        if replaceprojects:
+            args += " -rs "
+        else:
+            args += " -ks "
+        if result == None:
+            result = UpdateResult(self._session)
+        result = self._session.execute("update %s -project %s" % (args, self.objectname), result)
+        if not result.successful and not keepgoing:
+            raise CCMException("Error updating %s" % (self.objectname), result)
+        return result
+    
+    def reconcile(self, updatewa=True, recurse=True, consideruncontrolled=True, missingwafile=True, report=True):
+        """ Reconcile the project to force the work area to match the database. """
+        args = ""
+        if updatewa:
+            args += " -update_wa "
+        if recurse:
+            args += " -recurse "
+        if consideruncontrolled:
+            args += " -consider_uncontrolled "
+        if missingwafile:
+            args += " -missing_wa_file "
+        if report:
+            args += " -report reconcile.txt "
+        result = self._session.execute("reconcile %s -project %s" % (args, self.objectname), Result(self._session))
+        if re.search(r"There are no conflicts in the Work Area", result.output) == None and re.search(r"Reconcile completed", result.output) == None:
+            raise CCMException("Error reconciling %s,\n%s" % (self.objectname, result.output), result)        
+        return result.output
+
+    def get_latest_baseline(self, filterstring="*", state="released"):
+        """get_latest_baseline"""
+        result = self._session.execute("query -n %s -t project -f \"%%displayname\" -s %s -u -ns \"version smatch'%s'\"" % (self.name, state, filterstring))
+        lines = result.output.splitlines()
+        return lines[-1]
+
+    def create_baseline(self, baseline_name, release, baseline_tag, purpose="System Testing", state="published_baseline"):
+        """create_baseline"""
+        result = self._session.execute("baseline -create %s -release %s -purpose \"%s\" -vt %s -project \"%s\" -state \"%s\"" % (baseline_name, release, purpose, baseline_tag, self.objectname, state))
+        return result.output
+    
+    def sync(self, recurse=False, static=False):
+        """ Synchronize project content. By default it is not been done recusively. (Not unittested)"""
+        args = ""
+        if recurse:
+            args += " -recurse"
+        if static:
+            args += " -static"
+        result = self._session.execute("sync %s -project \"%s\"" % (args, self.objectname))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error during synchronization of %s: %s." % (self.objectname, result.output))
+        return result.output
+
+    def conflicts(self, recurse=False, tasks=False):
+        """conflicts"""
+        args = "-noformat "
+        if recurse:
+            args += " -r"
+        if tasks:
+            args += " -t"
+        
+        result = self._session.execute("conflicts %s  \"%s\"" % (args, self.objectname), ConflictsResult(self._session))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error during conflict detection of %s: %s." % (self.objectname, result))
+        return result
+    
+    tasks = property(_gettasks)
+    folders = property(_getfolders)
+    subprojects = property(_getsubprojects)
+    release = property(_getrelease, _setrelease)
+    baseline = property(_getbaseline, set_baseline)
+
+
+class Dir(CCMObject):
+    """ Wrapper class for Synergy dir object """
+    
+    def __init__(self, session, fpn):
+        CCMObject.__init__(self, session, fpn)
+
+    def children(self, project):
+        """children"""
+        assert(project.type == 'project')
+        result = self._session.execute("query \"is_child_of('%s','%s')\" -u -f \"%%objectname\"" % (self.objectname, project), ObjectListResult(self._session))
+        return result.output
+        
+
+class Releasedef(CCMObject):
+    """ Wrapper class for Synergy releasedef object """
+    
+    def __init__(self, session, fpn):
+        CCMObject.__init__(self, session, fpn)
+    
+    def _getcomponent(self):
+        """get component"""
+        return self.name
+            
+    component = property(_getcomponent)
+
+
+class Folder(CCMObject):
+    """ Wrapper class for Synergy folder object """
+    
+    def __init__(self, session, fpn):
+        CCMObject.__init__(self, session, fpn)
+
+    def _gettasks(self):
+        """ Accessor for 'tasks' property. """
+        result = self._session.execute("folder -show tasks \"%s\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
+        return result.output
+
+    def _getobjects(self):
+        """get objects"""
+        result = self._session.execute("folder -show objects \"%s\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
+        return result.output
+
+    def _getmode(self):
+        """ Get the mode used by the folder. """
+        result = self._session.execute("folder -show mode \"%s\"" % self.objectname)
+        return result.output.strip()
+
+    def _getquery(self):
+        """ Get the query that populate the folder. """
+        if self.mode.lower() == "query":
+            result = self._session.execute("folder -show query \"%s\"" % self.objectname)
+            return result.output.strip()
+        else:
+            raise CCMException("%s is not a query base folder." % (self.objectname))
+    
+    def _getdescription(self):
+        """ Get the description associated with the folder. """
+        result = self._session.execute("query -t folder -n %s -i %s -u -f \"%%description\"" % (self.name, self.instance))
+        return result.output.strip()
+
+    def remove(self, task):
+        """ Remove task from this folder. """
+        result = self._session.execute("folder -m \"%s\" -remove_task \"%s\"" % (self.objectname, task.objectname))
+        if result.status != None and result.status != 0:
+            raise CCMException("Error removing task %s from %s: %s." % (task.objectname, self.objectname, result.output))
+
+    def update(self):
+        """update"""
+        result = self._session.execute("folder -m -update -f \"%%objectname\"" % self.objectname)
+        if result.status != None and result.status != 0:
+            raise CCMException("Error updating the folder content %s: %s." % (self.objectname, result.output))
+        
+    def append(self, task):
+        """ Associate an object to a task """
+        class AddTaskException(CCMException):
+            """add task exception"""
+            def __init__(self, reason, task, result):
+                CCMException.__init__(self, reason, result)
+                self.task = task
+        
+        result = self._session.execute("folder -m -at \"%s\" \"%s\"" % (task.objectname, self.objectname))
+        if re.search(r"(Added 1 task to)|(is already in folder)", result.output, re.M) is None:
+            raise AddTaskException(result.output, result, task)
+    
+    def copy(self, existing_folder):
+        """ Copy the contents of existing_folder into this folder.
+        
+        This appends to the destination folder by default.
+        
+        :param existing_folder: The destination Folder object.
+        """
+        result = self._session.execute("folder -copy %s -existing %s -append" % (self.objectname, existing_folder), FolderCopyResult(self._session))
+        return result.output
+        
+    objects = property(_getobjects)
+    tasks = property(_gettasks)
+    mode = property(_getmode)
+    query = property(_getquery)
+    is_query_based = property(lambda x: x.mode.lower() == "query")
+    description = property(_getdescription)
+
+
+class Task(CCMObject):
+    """ Wrapper class for Synergy task object """
+    
+    def __init__(self, session, fpn):
+        CCMObject.__init__(self, session, fpn)
+        self.__unicode_str_text = None
+
+    def _getobjects(self):
+        """get objects"""
+        result = self._session.execute("task -show objects \"%s\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
+        return result.output
+    
+    def append(self, ccm_object):
+        """ Associate an object to a task """
+        class AddObjectException(CCMException):
+            """add object exception"""
+            def __init__(self, comment, ccm_object):
+                CCMException.__init__(self, comment)
+                self.ccm_object = ccm_object
+        
+        result = self._session.execute("task -associate \"%s\" -object \"%s\"" % (self.objectname, ccm_object.objectname))
+        if not re.match(r"Associated object .+ with task .*\.", result.output, re.M):
+            raise AddObjectException(result.output)
+
+    def assign(self, username):
+        """assign"""
+        result = self._session.execute("task -modify \"%s\" -resolver %s" % (self.objectname, username))
+        if not re.match(r"Changed resolver of task", result.output, re.M):
+            raise CCMException("Error assigning task to user '%s',\n%s" % (username, result.output), result)
+        
+    def _getsynopsis(self):
+        """get synopsis"""
+        return self['task_synopsis']
+        
+    @staticmethod
+    def create(session, release_tag, synopsis=""):
+        """create"""
+        assert release_tag.type == "releasedef", "release_tag must be a CCM object wrapper of releasedef type"
+        result = session.execute("task -create -synopsis \"%s\" -release \"%s\"" % (synopsis, release_tag['displayname']), CreateNewTaskResult(session))
+        return result.output
+        
+    objects = property(_getobjects)
+    
+    def __unicode__(self):
+        # TODO: use optimised query that makes only 1 ccm query with suitable format
+        if self.__unicode_str_text == None:
+            self.__unicode_str_text = u'%s: %s' % (self['displayname'], self['task_synopsis'])
+        return self.__unicode_str_text
+        
+    def __str__(self):
+        return self.__unicode__().encode('ascii', 'replace')
+    
+    def get_release_tag(self):
+        """ Get task release. Use release property!"""
+        result = self._session.execute("attribute -show release \"%s\"" % (self.objectname), Result(self._session))
+        return result.output
+    
+    def set_release_tag(self, release_tag):
+        """ Set task release. Use release property!"""
+        result = self._session.execute("attribute -modify release -value \"%s\" \"%s\"" % (release_tag, self.objectname), Result(self._session))
+        return result.output
+
+    release = property(get_release_tag, set_release_tag)
+
+class UpdateTemplate:
+    """ Allow to access Update Template property using Release and Purpose. """
+    def __init__(self, releasedef, purpose):
+        assert(releasedef != None)
+        assert(purpose != None)
+        self._releasedef = releasedef
+        self._purpose = purpose
+        
+    def objectname(self):
+        """ Return the objectname representing this virtual object. """
+        return "%s:%s" % (self._releasedef['displayname'], self._purpose)
+
+    def baseline_projects(self):
+        """ Query all projects for this UpdateTemplate. """
+        result = self._releasedef.session.execute("ut -sh baseline_projects \"%s\"" % self.objectname(), ObjectListResult(self._releasedef.session))
+        print result.output
+        return result.output
+
+    def information(self):
+        """ Query all projects for this UpdateTemplate. """
+        result = self._releasedef.session.execute("ut -sh information \"%s\"" % self.objectname(), UpdateTemplateInformation(self._releasedef.session))
+        print result.output
+        return result.output
+
+    def baseline_selection_mode(self):
+        """ The current Baseline selection mode """
+        result = self._releasedef.session.execute("ut -sh bsm \"%s\"" % self.objectname())
+        print result.output.strip()
+        return result.output.strip()
+
+
+def read_ccmwaid_info(filename):
+    """ Read data from a ccmwaid file. This method is an helper to retreive a project from a physical location. """
+    ccmwaid = open(filename, 'r')
+    # first line: database
+    dbpath = os.path.dirname(ccmwaid.readline().strip())
+    database = os.path.basename(dbpath)
+    # 2nd line should be a timestamp
+    ccmwaid.readline().strip()
+    # 3rd line is the objectname
+    objectref = ccmwaid.readline().strip()
+    ccmwaid.close()    
+    return {'dbpath': dbpath, 'database': database, 'objectname': objectref}
+
+def create_project_from_path(session, path):
+    """ Uses the (_|.)ccmwaid.inf file to create a Project object. """
+    ccmwaid = ".ccmwaid.inf"
+    if os.name == 'nt':
+        ccmwaid = "_ccmwaid.inf"
+        
+    if (not os.path.exists(path + "/" + ccmwaid)):
+        return None    
+    result = read_ccmwaid_info(path + "/" + ccmwaid)
+    
+    return session.create(result['objectname'])
+
+
+def open_session(username=None, password=None, engine=None, dbpath=None, database=None, reuse=True):
+    """Provides a Session object.
+    
+    Attempts to return a Session, based either on existing Synergy
+    sessions or by creating a new one.
+    
+    - If a .netrc file can be found on the user's personal drive,
+      that will be read to obtain Synergy login information if it 
+      is defined there. This will be used to fill in any missing 
+      parameters not passed in the call to open_session().
+      
+      The format of the .netrc file entries should be:
+      
+      machine synergy login USERNAME password foobar account DATABASE_PATH@SERVER
+      
+      If the details refer to a specific database, the machine can be the database name,
+      instead of "synergy".
+    - If an existing session is running that matches the supplied
+      parameters, it will reuse that.
+    
+    """
+    # See if a .netrc file can be used
+    if CCM_BIN == None:
+        raise CCMException("Could not find CM/Synergy executable in the path.")
+    if password == None or username == None or engine == None or dbpath == None:
+        if os.sep == '\\':
+            os.environ['HOME'] = "H:" + os.sep
+        _logger.debug('Opening .netrc file')
+        try:
+            netrc_file = netrc.netrc()
+            netrc_info = None
+            # If settings for a specific database 
+            if database != None:
+                netrc_info = netrc_file.authenticators(database)
+
+            # if not found just try generic one
+            if netrc_info == None:
+                netrc_info = netrc_file.authenticators('synergy')
+                
+            if netrc_info != None:
+                (n_username, n_account, n_password) = netrc_info
+                if username == None:
+                    username = n_username
+                if password == None:
+                    password = n_password
+                if n_account != None:
+                    (n_dbpath, n_engine) = n_account.split('@')
+                    if dbpath == None and n_dbpath is not None:
+                        _logger.info('Database path set using .netrc (%s)' % n_dbpath)
+                        dbpath = n_dbpath
+                    if engine == None and n_engine is not None:
+                        _logger.info('Database engine set using .netrc (%s)' % n_engine)
+                        engine = n_engine
+        except IOError:
+            _logger.debug('Error accessing .netrc file')
+
+    # last chance...
+    if username == None:
+        username = os.environ['USERNAME']
+
+    # looking for dbpath using GSCM database
+    if dbpath == None and database != None:
+        _logger.info('Database path set using the GSCM database.')
+        dbpath = nokia.gscm.get_db_path(database)
+
+    # looking for engine host using GSCM database
+    if engine == None and database != None:
+        _logger.info('Database engine set using the GSCM database.')
+        engine = nokia.gscm.get_engine_host(database)
+
+    _sessions = []
+    # See if any currently running sessions can be used, only if no password submitted, else use a brand new session!
+    if password == None and reuse:
+        _logger.debug('Querying for existing Synergy sessions')
+        command = "%s status" % (CCM_BIN)
+        pipe = os.popen(command, 'r')
+        result = pipe.read()
+        pipe.close()
+        _logger.debug('ccm status result: ' + result)
+        for match in re.finditer(r'(?P<ccmaddr>\w+:\d+:\d+.\d+.\d+.\d+(:\d+.\d+.\d+.\d+)?)(?P<current_session>\s+\(current\s+session\))?\nDatabase:\s*(?P<dbpath>\S+)', result, re.M):
+            dictionary = match.groupdict()
+            _logger.debug(dictionary['ccmaddr'])
+            _logger.debug(socket.gethostname())
+            _logger.debug(dictionary['current_session'])
+            if dictionary['ccmaddr'].lower().startswith(socket.gethostname().lower()):
+                # These session objects should not close the session on deletion,
+                # because they did not initially create the session
+                existing_session = Session(username, engine, dictionary['dbpath'], dictionary['ccmaddr'], close_on_exit=False)
+                _logger.debug('Existing session found: %s' % existing_session)
+                _sessions.append(existing_session)
+        # looking for session using dbpath
+        for session in _sessions:
+            if session.dbpath == dbpath:
+                return session
+    else:
+        # looking for router address using GSCM database
+        router_address = None
+        if database == None and dbpath != None:
+            database = os.path.basename(dbpath)
+        
+        lock = fileutils.Lock(CCM_SESSION_LOCK)
+        try:
+            lock.lock(wait=True)
+            # if we have the database name we can switch to the correct Synergy router
+            if database != None:
+                _logger.info('Getting router address.')
+                router_address = nokia.gscm.get_router_address(database)
+                if os.sep == '\\' and router_address != None:
+                    routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), 'r')
+                    current_router = routerfile.read().strip()
+                    routerfile.close()
+                    if current_router != router_address.strip():
+                        _logger.info('Updating %s' % (os.path.normpath(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"))))
+                        routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), "w+")
+                        routerfile.write("%s\n" % router_address)
+                        routerfile.close()
+        
+            # If no existing sessions were available, start a new one
+            _logger.info('Opening session.')
+            new_session = Session.start(username, password, engine, dbpath)
+            lock.unlock()
+            return new_session
+        finally:
+            lock.unlock()
+    raise CCMException("Cannot open session for user '%s'" % username)
+
+
+def get_role_for_purpose(session, purpose):
+    """  return role needed to modify project with checkout for purpose. """
+    purposes = session.purposes()
+    if purpose in purposes:
+        if purposes[purpose]['status'] == 'prep':
+            return 'build_mgr'
+    else:
+        raise CCMException("Could not find purpose '%s' in the database.\n Valid purpose are: %s." % (purpose, ','.join(purposes.keys())))
+    return 'developer'
+
+def get_role_for_status(status):
+    """  return role needed to modify project with a specific status. """
+    if status == 'prep':
+        return 'build_mgr'
+    elif status == 'shared':
+        return 'developer'
+    elif status == 'working':
+        return 'developer'
+    else:
+        raise CCMException("Unknow status '%s'" % status)
+
+def running_sessions(database=None):
+    """ Return the list of synergy session currently available on the local machine.
+        If database is given then it tries to update the router address.
+    """
+    _logger.debug('Querying for existing Synergy sessions')
+    if CCM_BIN == None:
+        raise CCMException("Could not find CM/Synergy executable in the path.")
+    command = "%s status" % (CCM_BIN)
+
+    lock = fileutils.Lock(CCM_SESSION_LOCK)
+    result = ""
+    output = []
+    try:
+        # if we have the database name we can switch to the correct Synergy router
+        if database != None:
+            lock.lock(wait=True)
+            _logger.info('Updating router address.')
+            router_address = nokia.gscm.get_router_address(database)
+            if os.sep == '\\' and router_address != None:
+                routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), 'r')
+                current_router = routerfile.read().strip()
+                routerfile.close()
+                if current_router != router_address.strip():
+                    _logger.info('Updating %s' % (os.path.normpath(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"))))
+                    routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), "w+")
+                    routerfile.write("%s\n" % router_address)
+                    routerfile.close()
+
+        _logger.debug('Command: ' + command)
+        (result, status) = _execute(command)
+        if database != None:
+            lock.unlock()
+        if (status != 0):
+            raise CCMException("Ccm status execution returned an error.")
+        _logger.debug('ccm status result: ' + result)
+        for match in re.finditer(r'Command Interface\s+@\s+(?P<ccmaddr>\w+:\d+:\d+.\d+.\d+.\d+(:\d+.\d+.\d+.\d+)*)(?P<current_session>\s+\(current\s+session\))?\s+Database:\s*(?P<dbpath>\S+)', result, re.M):
+            data = match.groupdict()
+            _logger.debug(data['ccmaddr'])
+            _logger.debug(socket.gethostname())
+            _logger.debug(data['current_session'])
+            if data['ccmaddr'].lower().startswith(socket.gethostname().lower()):
+                # These session objects should not close the session on deletion,
+                # because they did not initially create the session
+                existing_session = Session(None, None, data['dbpath'], data['ccmaddr'], close_on_exit=False)
+                _logger.debug('Existing session found: %s' % existing_session)
+                output.append(existing_session)
+    finally:
+        if database != None:
+            lock.unlock()
+    return  output
+
+def session_exists(sessionid, database=None):
+    """determine if session exists"""
+    for session in running_sessions(database=database):
+        _logger.debug(session.addr() + "==" + sessionid + "?")
+        if session.addr() == sessionid:
+            return True
+    return False
+
+# The location of the ccm binary must be located to know where the _router.adr file is, to support
+# switching databases.
+CCM_BIN = fileutils.which("ccm")
+if os.sep == '\\':
+    CCM_BIN = fileutils.which("ccm.exe")