buildframework/helium/sf/python/pythoncore/lib/ccm/__init__.py
changeset 587 85df38eb4012
child 588 c7c26511138f
child 618 df88fead2976
equal deleted inserted replaced
217:0f5e3a7fb6af 587:85df38eb4012
       
     1 #============================================================================ 
       
     2 #Name        : __init__.py 
       
     3 #Part of     : Helium 
       
     4 
       
     5 #Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     6 #All rights reserved.
       
     7 #This component and the accompanying materials are made available
       
     8 #under the terms of the License "Eclipse Public License v1.0"
       
     9 #which accompanies this distribution, and is available
       
    10 #at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
    11 #
       
    12 #Initial Contributors:
       
    13 #Nokia Corporation - initial contribution.
       
    14 #
       
    15 #Contributors:
       
    16 #
       
    17 #Description:
       
    18 #===============================================================================
       
    19 
       
    20 """ CM/Synergy Python toolkit.
       
    21 
       
    22 """
       
    23 
       
    24 import logging
       
    25 import netrc
       
    26 import os
       
    27 import re
       
    28 import subprocess
       
    29 import threading
       
    30 
       
    31 import fileutils
       
    32 import nokia.gscm
       
    33 import tempfile
       
    34 import socket
       
    35 
       
    36 # Uncomment this line to enable logging in this module, or configure logging elsewhere
       
    37 _logger = logging.getLogger("ccm")
       
    38 #logging.basicConfig(level=logging.DEBUG)
       
    39 
       
    40 
       
    41 VALID_OBJECT_STATES = ('working', 'checkpoint', 'public', 'prep', 'integrate', 'sqa', 'test','released')
       
    42 STATIC_OBJECT_STATES = ('integrate', 'sqa', 'test','released')
       
    43 CCM_SESSION_LOCK = os.path.join(tempfile.gettempdir(), "ccm_session.lock")
       
    44 
       
    45 def _execute(command, timeout=None):
       
    46     """ Runs a command and returns the result data. """
       
    47     targ = ""
       
    48     if timeout is not None:
       
    49         targ = "--timeout=%s" % timeout
       
    50     process = subprocess.Popen("python -m timeout_launcher %s -- %s" % (targ, command), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
       
    51     stdout = process.communicate()[0]
       
    52     process.wait()
       
    53     _logger.debug(stdout)
       
    54     _logger.debug("Return code: %s" % process.returncode)
       
    55     return (stdout, process.returncode)
       
    56    
       
    57    
       
    58 class CCMException(Exception):
       
    59     """ Base exception that should be raised by methods of this framework. """
       
    60     def __init__(self, reason, result = None):
       
    61         Exception.__init__(self, reason)
       
    62         self.result = result
       
    63 
       
    64 
       
    65 class Result(object):
       
    66     """Class that abstracts ccm call result handling.
       
    67     
       
    68     Subclass it to implement a new generic output parser.
       
    69     """
       
    70     def __init__(self, session):
       
    71         self._session = session
       
    72         self.status = None
       
    73         self._output = None
       
    74         self._output_str = None
       
    75     
       
    76     def _setoutput(self, output):
       
    77         """set output"""
       
    78         self._output = output
       
    79         
       
    80     def __setoutput(self, output):
       
    81         """ Internal function to allow overloading, you must override _setoutput.
       
    82         """
       
    83         # the output is automatically converted to ascii before any treatment 
       
    84         if isinstance(output, unicode):
       
    85             self._output_str = output.encode('ascii', 'replace')
       
    86         else:
       
    87             self._output_str = output.decode('ascii', 'ignore')
       
    88         _logger.debug("output ---->")
       
    89         for line in self._output_str.splitlines():
       
    90             _logger.debug(line)
       
    91         _logger.debug("<----")
       
    92         self._setoutput(self._output_str)
       
    93                 
       
    94     def _getoutput(self):
       
    95         """ Returns the content of _output. """
       
    96         return self._output
       
    97         
       
    98     def __str__(self):
       
    99         """ Synergy output log. """
       
   100         return self._output_str.encode('ascii', 'replace')
       
   101         
       
   102     output = property(_getoutput, __setoutput)
       
   103 
       
   104 class ResultWithError(Result):
       
   105     """ A result class to parse output errors """
       
   106     def __init__(self, session):
       
   107         Result.__init__(self, session)
       
   108         self._error = None
       
   109         self._error_str = None    
       
   110 
       
   111     def _seterror(self, error):
       
   112         """set the error """
       
   113         self._error = error
       
   114         
       
   115     def __seterror(self, error):
       
   116         """ Internal function to allow overloading, you must override _seterror.
       
   117         """
       
   118         # the error output is automatically converted to ascii before any treatment 
       
   119         if isinstance(error, unicode):
       
   120             self._error_str = error.encode('ascii', 'replace')
       
   121         else:
       
   122             self._error_str = error.decode('ascii', 'ignore')
       
   123         _logger.debug("error ---->")
       
   124         for line in self._error_str.splitlines():
       
   125             _logger.debug(line)
       
   126         _logger.debug("<----")
       
   127         self._seterror(self._error_str)
       
   128                 
       
   129     def _geterror(self):
       
   130         """ Returns the content of _output. """
       
   131         _logger.debug("_geterror")
       
   132         return self._error
       
   133 
       
   134     error = property(_geterror, __seterror)
       
   135     
       
   136 class ProjectCheckoutResult(Result):
       
   137     """ Project checkout output parser. 
       
   138         Sets project to the created project or None if failed.
       
   139     """
       
   140     def __init__(self, session, project):
       
   141         Result.__init__(self, session)
       
   142         self.__project = project
       
   143         self.__result_project = None
       
   144     
       
   145     def _setoutput(self, output):
       
   146         """ Parsing the output of the checkout command. """
       
   147         self._output = output
       
   148         for line in output.splitlines():
       
   149             mresult = re.match(r"Saved work area options for project: '(.+)'", line, re.I)
       
   150             #(?P<name>.+)-(?P<version>.+?)(:(?P<type>\S+):(?P<instance>\S+))?
       
   151             if mresult != None:
       
   152                 #self.__project.name + "-" + mo.groupdict()['version'] + ":" + self.__project.type + ":" + self.__project.instance
       
   153                 self.__result_project = self._session.create(mresult.group(1))
       
   154                 _logger.debug("ProjectCheckoutResult: project: '%s'" % self.__result_project)
       
   155                 return
       
   156 
       
   157     def __get_result_project(self):
       
   158         """ return the checked out project. """
       
   159         return self.__result_project
       
   160     
       
   161     project = property(__get_result_project)
       
   162 
       
   163 
       
   164 class ProjectPurposeResult(Result):
       
   165     """ Parses purpose query output. """
       
   166     def __init__(self, session):
       
   167         Result.__init__(self, session)
       
   168 
       
   169     def _setoutput(self, output):
       
   170         """set output """
       
   171         self._output = {}
       
   172         for line in output.splitlines():
       
   173             mresult = re.match(r"(?P<purpose>.+?)\s+(?P<member_status>\w+)\s+(?P<status>\w+)$", line)
       
   174             if mresult != None:
       
   175                 data = mresult.groupdict()                
       
   176                 if re.match(r'^\s+Purpose\s+Member$', data['purpose'], re.I) == None:
       
   177                     self._output[data['purpose'].strip()] = {'member_status' : data['member_status'].strip(),
       
   178                                                   'status' : data['status'].strip()}
       
   179 
       
   180 class ConflictsResult(Result):
       
   181     """ Parses purpose query output. """
       
   182     def __init__(self, session):
       
   183         Result.__init__(self, session)
       
   184 
       
   185     def _setoutput(self, output):
       
   186         """ set output """
       
   187         self._output = {}
       
   188         project = None
       
   189                 
       
   190         for line in output.splitlines():
       
   191             mresult = re.match(r"Project:\s*(.+)\s*$", line)
       
   192             if mresult != None:
       
   193                 project = self._session.create(mresult.group(1))
       
   194                 self._output[project] = []
       
   195             mresult = re.match(r"^(.*)\s+(\w+#\d+)\s+(.+)$", line)
       
   196             if mresult != None and project != None:
       
   197                 self._output[project].append({'object': self._session.create(mresult.group(1)),
       
   198                                               'task': self._session.create("Task %s" % mresult.group(2)),
       
   199                                               'comment': mresult.group(3)})
       
   200             mresult = re.match(r"^(\w+#\d+)\s+(.+)$", line)
       
   201             if mresult != None and project != None:
       
   202                 self._output[project].append({'task': self._session.create("Task %s" % mresult.group(1)),
       
   203                                               'comment': mresult.group(2)})
       
   204 
       
   205 
       
   206 class FinduseResult(Result):
       
   207     """ Parses finduse query output. """
       
   208     def __init__(self, ccm_object):
       
   209         Result.__init__(self, ccm_object.session)
       
   210         self.__object = ccm_object
       
   211 
       
   212     def _setoutput(self, output):
       
   213         """set output"""
       
   214         self._output = []
       
   215         for line in output.splitlines():
       
   216             _logger.debug("FinduseResult: ---->%s<----" % line)
       
   217             _logger.debug("FinduseResult: ---->%s-%s<----" % (self.__object.name, self.__object.version))
       
   218             
       
   219             # MCNaviscroll\NaviAnim-username7@MCNaviscroll-username6            
       
   220             mresult = re.match(r"^\s*(?P<path>.+)[\\/]%s-%s@(?P<project>.+)" % (self.__object.name, self.__object.version), line, re.I)
       
   221             if mresult != None:
       
   222                 data = mresult.groupdict()
       
   223                 _logger.debug("FinduseResult: %s" % data)               
       
   224                 project = self._session.create(data['project'])
       
   225                 self._output.append({'path' : data['path'], 'project' : project})
       
   226         
       
   227         
       
   228 class UpdateTemplateInformation(Result):
       
   229     """ Parse update template information output. """
       
   230     def __init__(self, session):
       
   231         Result.__init__(self, session)
       
   232     
       
   233     def _setoutput(self, output):
       
   234         """
       
   235 Baseline Selection Mode: Latest Baseline Projects
       
   236 Prep Allowed:            No
       
   237 Versions Matching:       *abs.50*
       
   238 Release Purposes:
       
   239 Use by Default:          Yes
       
   240 Modifiable in Database:  tr1s60
       
   241 In Use For Release:      Yes
       
   242 Folder Templates and Folders:
       
   243 - Template assigned or completed tasks for %owner for release %release
       
   244 - Template all completed tasks for release %release
       
   245 - Folder   tr1s60#4844: All completed Xuikon/Xuikon_rel_X tasks
       
   246 - Folder   tr1s60#4930: All tasks for release AppBaseDo_50        
       
   247         """
       
   248         self._output = {}
       
   249         for line in output.splitlines():
       
   250             rmo = re.match(r"^\s*(.+):\s*(.*)\s*", line)
       
   251             if rmo != None:
       
   252                 if rmo.group(1) == "Baseline Selection Mode":
       
   253                     self._output['baseline_selection_mode'] = rmo.group(2) 
       
   254                 elif rmo.group(1) == "Prep Allowed":
       
   255                     self._output['prep_allowed'] = (rmo.group(2) != "No") 
       
   256                 elif rmo.group(1) == "Versions Matching":
       
   257                     self._output['version_matching'] = rmo.group(2) 
       
   258                 elif rmo.group(1) == "Release Purposes":
       
   259                     self._output['release_purpose'] = rmo.group(2) 
       
   260                 elif rmo.group(1) == "Use by Default":
       
   261                     self._output['default'] = (rmo.group(2) != "No") 
       
   262                 elif rmo.group(1) == "Modifiable in Database":
       
   263                     self._output['modifiable_in_database'] = rmo.group(2).strip()
       
   264                 elif rmo.group(1) == "In Use For Release":
       
   265                     self._output['in_use_for_release'] = (rmo.group(2) != "No") 
       
   266                 
       
   267 
       
   268 class UpdatePropertiesRefreshResult(Result):
       
   269     """ Parse update template refresh output. """
       
   270     def __init__(self, session):
       
   271         Result.__init__(self, session)
       
   272 
       
   273     def _setoutput(self, output):
       
   274         """set output"""
       
   275         self._output = {'added': [], 'removed': []}
       
   276         match_added = re.compile(r"^Added the following tasks")
       
   277         match_removed = re.compile(r"^Removed the following tasks")
       
   278         match_task_new = re.compile(r"^\s+(Task \S+#\d+)")        
       
   279         section = None
       
   280                 
       
   281         for line in output.splitlines():
       
   282             res = match_added.match(line)
       
   283             if res != None:
       
   284                 section = 'added'
       
   285                 continue
       
   286             res = match_removed.match(line)
       
   287             if res != None:
       
   288                 section = 'removed'
       
   289                 continue
       
   290             if section is not None:
       
   291                 res = match_task_new.match(line)
       
   292                 if res != None:
       
   293                     self._output[section].append(self._session.create(res.group(1)))
       
   294                     continue
       
   295 
       
   296 
       
   297 class UpdateResultSimple(Result):
       
   298     """ Parse update output. """
       
   299     def __init__(self, session):
       
   300         Result.__init__(self, session)
       
   301         self._success = True
       
   302 
       
   303     def _setoutput(self, output):
       
   304         """set output"""
       
   305         self._output = output
       
   306         match_failed = re.compile(r"(Update failed)")
       
   307         for line in output.splitlines():
       
   308             res = match_failed.match(line)
       
   309             if res != None:
       
   310                 self._success = False
       
   311 
       
   312     @property
       
   313     def successful(self):
       
   314         """successful"""
       
   315         return self._success
       
   316 
       
   317 class UpdateResult(UpdateResultSimple):
       
   318     """ Parse update output. """
       
   319     def __init__(self, session):
       
   320         UpdateResultSimple.__init__(self, session)
       
   321 
       
   322     def _setoutput(self, output):
       
   323         """set output"""
       
   324         self._output = {"tasks":[], "modifications": [], "errors": [], "warnings": []}
       
   325         match_object_update = re.compile(r"^\s+'(.*)'\s+replaces\s+'(.*)'\s+under\s+'(.*)'\.")
       
   326         match_object_new = re.compile(r"^\s+(?:Subproject\s+)?'(.*)'\s+is now bound under\s+'(.*)'\.")
       
   327         match_task_new = re.compile(r"^\s+(Task \S+#\d+)")
       
   328         match_no_candidate = re.compile(r"^\s+(.+) in project (.+) had no candidates")
       
   329         match_update_failure = re.compile(r"^\s+Failed to use selected object\s+(.+)\s+under directory\s+(.+)\s+in project\s+(.+)\s+:\s+(.+)")
       
   330         match_warning = re.compile(r"^Warning:(.*)")
       
   331         match_failed = re.compile(r"(Update failed)")
       
   332         
       
   333         # TODO: cleanup the parsing to do that in a more efficient way.
       
   334         for line in output.splitlines():
       
   335             _logger.info(line)
       
   336             res = match_object_update.match(line)
       
   337             if res != None:
       
   338                 self._output['modifications'].append({ "new": self._session.create(res.group(1)),
       
   339                                       "old": self._session.create(res.group(2)),
       
   340                                       "project": self._session.create(res.group(3))})
       
   341                 continue
       
   342             res = match_object_new.match(line)
       
   343             if res != None:                
       
   344                 self._output['modifications'].append({ "new": self._session.create(res.group(1)),
       
   345                                       "old": None,
       
   346                                       "project": self._session.create(res.group(2))})
       
   347                 continue
       
   348             res = match_task_new.match(line)
       
   349             if res != None:                
       
   350                 self._output['tasks'].append(self._session.create(res.group(1)))
       
   351                 continue
       
   352             res = match_no_candidate.match(line)
       
   353             if res != None:                
       
   354                 self._output['errors'].append({'family': res.group(1),
       
   355                                                'project': self._session.create(res.group(2)),
       
   356                                                'comment': "had no candidates",
       
   357                                                'line': line,})
       
   358                 continue
       
   359             res = match_update_failure.match(line)
       
   360             if res != None:                
       
   361                 self._output['errors'].append({'family': res.group(1),
       
   362                                                'dir': self._session.create(res.group(2)),
       
   363                                                'project': self._session.create(res.group(3)),
       
   364                                                'comment': res.group(4),
       
   365                                                'line': line,
       
   366                                                })
       
   367                 continue
       
   368             res = match_warning.match(line)            
       
   369             if res != None:                
       
   370                 self._output['warnings'].append({'family': None,
       
   371                                                'project': None,
       
   372                                                'comment': res.group(1),
       
   373                                                'line': line,
       
   374                                                })
       
   375                 continue
       
   376             res = match_failed.match(line)
       
   377             if res != None:
       
   378                 self._success = False
       
   379                 self._output['errors'].append({'Serious': res.group(1),
       
   380                                                })
       
   381                 continue
       
   382                 
       
   383             
       
   384 
       
   385 class WorkAreaInfoResult(Result):
       
   386     """ Parse work area info output. """
       
   387     def __init__(self, session):
       
   388         Result.__init__(self, session)
       
   389 
       
   390     def _setoutput(self, output):
       
   391         """ Returns a dict with the following fields:
       
   392                * project: a ccm.Project instance
       
   393                * maintain: a boolean
       
   394                * copies: a boolean
       
   395                * relative: a boolean
       
   396                * time: a boolean
       
   397                * translate: a boolean
       
   398                * modify: a boolean
       
   399                * path: a string representing the project wa path
       
   400         """
       
   401         self._output = None
       
   402         for line in output.splitlines():
       
   403             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)            
       
   404             if mresult != None:
       
   405                 data = mresult.groupdict()
       
   406                 self._output = {'project': self._session.create(data['project']),
       
   407                                 'maintain' : data['maintain'] == "TRUE",
       
   408                                 'copies' : data['copies'] == "TRUE",
       
   409                                 'relative' : data['relative'] == "TRUE",
       
   410                                 'time' : data['time'] == "TRUE",
       
   411                                 'translate' : data['translate'] == "TRUE",
       
   412                                 'modify' : data['modify'] == "TRUE",
       
   413                                 'path' : data['path']
       
   414                                 }
       
   415                 return
       
   416 
       
   417 
       
   418 class CreateNewTaskResult(Result):
       
   419     """ A result class to parse newly created tasks """
       
   420     def __init__(self, session):
       
   421         Result.__init__(self, session)
       
   422 
       
   423     def _setoutput(self, output):
       
   424         """set output"""
       
   425         self._output = None
       
   426         for line in output.splitlines():
       
   427             mresult = re.match(r"Task\s+(?P<task>\S+\#\d+)\s+created\.", line)
       
   428             if mresult != None:
       
   429                 self._output = self._session.create("Task " + mresult.groupdict()['task'])
       
   430                 return
       
   431 
       
   432 
       
   433 class AttributeNameListResult(Result):
       
   434     """ Class that abstract ccm call result handling.
       
   435         Subclass it to implement a new generic output parser.
       
   436     """
       
   437     def __init__(self, session):
       
   438         Result.__init__(self, session)
       
   439 
       
   440     def _setoutput(self, obj):
       
   441         """set output"""
       
   442         def _create(arg):
       
   443             """create"""
       
   444             mresult = re.match(r"^\s*(?P<name>\w+)", arg.strip())
       
   445             if mresult != None:
       
   446                 return mresult.groupdict()['name']
       
   447             return None
       
   448         self._output = [_create(line) for line in obj.strip().splitlines()]
       
   449 
       
   450 
       
   451 class ObjectListResult(Result):
       
   452     """ Parses an object list Synergy output. """
       
   453     def __init__(self, session):
       
   454         Result.__init__(self, session)
       
   455 
       
   456     def _setoutput(self, obj):        
       
   457         """set output"""
       
   458         self._output = []
       
   459         if re.match(r"^None|^No tasks|^Warning", obj, re.M) != None:
       
   460             return
       
   461         def _create(arg):
       
   462             """create"""
       
   463             arg = arg.strip()
       
   464             if arg != "":
       
   465                 return self._session.create(arg)
       
   466             return None
       
   467         result = [_create(line) for line in obj.strip().splitlines()]
       
   468         for result_line in result:
       
   469             if result_line != None:
       
   470                 self._output.append(result_line)
       
   471 
       
   472 class DataMapperListResult(Result):
       
   473     """ Parses an object list Synergy output. """
       
   474     
       
   475     dataconv = {'ccmobject': lambda x, y: x.create(y),
       
   476                 'string': lambda x, y: y,
       
   477                 'int': lambda x, y: int(y),
       
   478                 'boolean': lambda x, y: (y.lower() == "true")}
       
   479 
       
   480     def __init__(self, session, separator, keywords, datamodel):
       
   481         self._separator = separator
       
   482         self._keywords = keywords
       
   483         self._datamodel = datamodel
       
   484         Result.__init__(self, session)
       
   485 
       
   486     def format(self):
       
   487         """format keywords"""
       
   488         formatted_keywords = ["%s%s%s%%%s" % (self._separator, x, self._separator, x) for x in self._keywords]
       
   489         return "".join(formatted_keywords) + self._separator
       
   490 
       
   491     def regex(self):
       
   492         """regular expression"""
       
   493         regex_keywords = [r'%s%s%s(.*?)' % (self._separator, x, self._separator) for x in self._keywords]
       
   494         regex = r''.join(regex_keywords)
       
   495         regex = r"%s%s\s*\n" % (regex, self._separator)
       
   496         return re.compile(regex, re.MULTILINE | re.I | re.DOTALL | re.VERBOSE | re.U)
       
   497 
       
   498     def _setoutput(self, obj):
       
   499         """set output"""
       
   500         self._output = []
       
   501         regex = self.regex()
       
   502         _logger.debug("Regex %s" % (regex.pattern))
       
   503         for match in regex.finditer(obj):
       
   504             _logger.debug("Found: %s" % (match))
       
   505             if match != None:
       
   506                 output_line = {}
       
   507                 for i in range(len(self._datamodel)):
       
   508                     _logger.debug("Found %d: %s" % (i, match.group(i + 1)))
       
   509                     model = self._datamodel[i]
       
   510                     output_line[self._keywords[i]] = self.dataconv[model](self._session, match.group(i + 1))
       
   511                     i += 1
       
   512                 self._output.append(output_line)
       
   513                 
       
   514 
       
   515 class FolderCopyResult(Result):
       
   516     """ Parses a folder copy result """
       
   517     def __init__(self, session):
       
   518         Result.__init__(self, session)
       
   519 
       
   520     def _setoutput(self, output):
       
   521         """set output"""
       
   522         self._output = None
       
   523         for line in output.splitlines():
       
   524             match_output = re.match(r"appended to", line)
       
   525             if match_output != None:
       
   526                 self._output = self._session.create(line)
       
   527                 return
       
   528 
       
   529 CHECKOUT_LOG_RULES = [[r'^Derive failed for', logging.ERROR],
       
   530                       [r'^Serious:', logging.ERROR],
       
   531                       [r'^Warning: .* failed.', logging.ERROR],
       
   532                       [r'^Invalid work area', logging.ERROR],
       
   533                       [r'^WARNING:', logging.WARNING],
       
   534                       [r'^Warning:', logging.WARNING],]
       
   535 
       
   536 
       
   537 UPDATE_LOG_RULES = [[r'^Update failed.', logging.ERROR],
       
   538                     [r'^Serious:', logging.ERROR],
       
   539                     [r'^\s+Failed to', logging.ERROR],
       
   540                     [r'^\d+ failures to', logging.ERROR],
       
   541                     [r"^Warning: This work area '.+' cannot be reused", logging.ERROR],
       
   542                     [r'^Rebind of .* failed', logging.ERROR],
       
   543                     [r'^Warning: .* failed.', logging.ERROR],
       
   544                     [r'^Skipping \'.*\'\.  You do not have permission to modify this project.', logging.ERROR],
       
   545                     [r'^Work area conflict exists for file', logging.ERROR],
       
   546                     [r'^Warning:  No candidates found for directory entry', logging.ERROR],
       
   547                     [r'^WARNING:', logging.WARNING],
       
   548                     [r'^Warning:', logging.WARNING],]
       
   549 
       
   550 CONFLICTS_LOG_RULES = [[r'^\w+#\d+\s+Implicit', logging.WARNING],
       
   551                        [r'^(.*)\s+(\w+#\d+)\s+(.+)', logging.WARNING],
       
   552                        [r'.*Explicitly specified but not included', logging.WARNING],]
       
   553 
       
   554 SYNC_LOG_RULES = [[r'^\s+0\s+Conflict\(s\) for project', logging.INFO],
       
   555                   [r'^\s+\d+\s+Conflict\(s\) for project', logging.ERROR],
       
   556                   [r'^Project \'.*\' does not maintain a workarea.', logging.ERROR],
       
   557                   [r'^Work area conflict exists for file', logging.ERROR],
       
   558                   [r'^Warning: Conflicts detected during synchronization. Check your logs.', logging.ERROR],
       
   559                   [r'^Warning:', logging.WARNING],]
       
   560 
       
   561 def log_result(result, rules, logger=None):
       
   562     """ Rules it a list of tuple defining a regular expression and an log level. """
       
   563     if logger is None:
       
   564         logger = _logger
       
   565     crules = []
       
   566     if rules is not None:
       
   567         for rule in rules:
       
   568             crules.append([re.compile(rule[0]), rule[1]])
       
   569                 
       
   570     for line in str(result).splitlines():
       
   571         for rule in crules:
       
   572             if rule[0].match(line) != None:
       
   573                 logger.log(rule[1], line)
       
   574                 break
       
   575         else:
       
   576             logger.info(line)
       
   577     
       
   578 class AbstractSession(object):
       
   579     """An abstract Synergy session.
       
   580 
       
   581     Must be overridden to implement either a single session or
       
   582     multiple session handling.
       
   583     """
       
   584     def __init__(self, username, engine, dbpath, ccm_addr):
       
   585         self.username = username
       
   586         self.engine = engine
       
   587         self.dbpath = dbpath
       
   588         self._session_addr = ccm_addr
       
   589         # internal object list
       
   590         self.__ccm_objects = {}
       
   591     
       
   592     def addr(self):
       
   593         """ Returns the Synergy session id."""
       
   594         return self._session_addr
       
   595     
       
   596     def database(self):
       
   597         """database"""
       
   598         _logger.debug("AbstractSession: database")
       
   599         self.__find_dbpath()
       
   600         _logger.debug("AbstractSession: database: %s" % self.dbpath)
       
   601         return os.path.basename(self.dbpath)
       
   602     
       
   603     def __find_dbpath(self):
       
   604         """ retrieve the database path from current session status. """
       
   605         _logger.debug("AbstractSession: __find_dbpath")
       
   606         if (self.dbpath != None):            
       
   607             return
       
   608         result = self.execute("status")
       
   609         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):
       
   610             dictionary = match.groupdict()
       
   611             if (dictionary['current_session'] != None):
       
   612                 _logger.debug("AbstractSession: __find_dbpath: Found dbpath: %s" % dictionary['dbpath'])
       
   613                 self.dbpath = dictionary['dbpath']
       
   614         assert self.dbpath != None
       
   615     
       
   616     def execute(self, _, result=None):
       
   617         """ Abstract function that should implement the execution of ccm command
       
   618             line call.
       
   619         """
       
   620         return result
       
   621 
       
   622     def create(self, fpn):
       
   623         """ Object factory, this is the toolkit entry point to create objects from
       
   624             four part names. Objects are stored into a dictionary, so you have
       
   625             only one wrapper per synergy object.
       
   626         """
       
   627         result = re.search(r"^(?P<project>.+)-(?P<version>[^:]+?)$", fpn)
       
   628         if result != None:
       
   629             matches = result.groupdict()
       
   630             fpn = "%s-%s:project:%s#1" % (matches['project'], matches['version'], self.database())
       
   631         _logger.debug("session.create('%s')" % fpn)
       
   632         ofpn = FourPartName(fpn)
       
   633         if not self.__ccm_objects.has_key(str(fpn)):
       
   634             obj = None
       
   635             if ofpn.type == 'project':
       
   636                 obj = Project(self, fpn)
       
   637             elif ofpn.type == 'dir':
       
   638                 obj = Dir(self, fpn)
       
   639             elif ofpn.type == 'task':
       
   640                 obj = Task(self, fpn)
       
   641             elif ofpn.type == 'folder':
       
   642                 obj = Folder(self, fpn)
       
   643             elif ofpn.type == 'releasedef':
       
   644                 obj = Releasedef(self, fpn)
       
   645             else:
       
   646                 obj = File(self, fpn)
       
   647             self.__ccm_objects[str(fpn)] = obj
       
   648         return self.__ccm_objects[str(fpn)]
       
   649 
       
   650     def get_workarea_info(self, dir_):
       
   651         """ Return a dictionary containing workarea info from directory dir.
       
   652         """
       
   653         if (not os.path.exists(dir_)):
       
   654             raise CCMException("Error retrieving work_area info for the directory '%s' (doesn't exists)" % dir_)
       
   655         path = os.path.abspath(os.path.curdir)
       
   656         path_ccmwaid = os.path.join(dir_,"_ccmwaid.inf")
       
   657         if(not os.path.exists(path_ccmwaid)):
       
   658             raise CCMException("No work area in '%s'" % dir_)
       
   659         os.chdir(dir_)
       
   660         result = self.execute("wa -show", WorkAreaInfoResult(self))
       
   661         os.chdir(path)
       
   662         if result.output == None:
       
   663             raise CCMException("Error retrieving work_area info for the directory '%s'" % dir_)
       
   664         return result.output
       
   665 
       
   666     def _get_role(self):
       
   667         """get CCM role"""
       
   668         result = self.execute("set role")
       
   669         return result.output.strip()
       
   670     
       
   671     def _set_role_internal(self, role):
       
   672         """ method to be override by child class else property accession is not working properly. """
       
   673         if  role == None or len(role) == 0:
       
   674             raise CCMException("You must provide a role.")
       
   675         result = self.execute("set role %s" % role)
       
   676         if re.match(r'^Warning:', result.output, re.M) != None:
       
   677             raise CCMException("Error switching to role %s: %s" %(role, result.output.strip()))
       
   678 
       
   679     def _set_role(self, role):
       
   680         """set CCM role"""
       
   681         self._set_role_internal(role)
       
   682         
       
   683     role = property(fget=_get_role, fset=_set_role)
       
   684     
       
   685     def _get_home(self):
       
   686         """get home"""
       
   687         result = self.execute("set Home")
       
   688         return result.output.strip()
       
   689         
       
   690     def _set_home(self, home):
       
   691         """set home"""
       
   692         if len(home) == 0 or home == None:
       
   693             raise CCMException("You must provide a home.")
       
   694         result = self.execute("set Home %s" % home)
       
   695         if re.match(r'^Warning:', result.output, re.M) != None:
       
   696             raise CCMException("Error switching to Home %s: %s" %(home, result.output.strip()))
       
   697     
       
   698     home = property(_get_home, _set_home)
       
   699     
       
   700     def close(self):
       
   701         """close does nothing"""
       
   702         pass
       
   703     
       
   704     def __str__(self):
       
   705         self.__find_dbpath()
       
   706         return self._session_addr + ':' + self.dbpath
       
   707         
       
   708     def __repr__(self):
       
   709         return self.__str__()
       
   710     
       
   711     def __del__(self):
       
   712         self.close()
       
   713 
       
   714     def purposes(self, role=None):
       
   715         """ Returns available purposes. """
       
   716         args = ""
       
   717         if role != None:
       
   718             args = "-role \"%s\"" % role
       
   719         result = self.execute("project_purpose -show %s" % args, ProjectPurposeResult(self))
       
   720         return result.output        
       
   721 
       
   722 class Session(AbstractSession):
       
   723     """A Synergy session.
       
   724     """
       
   725     def __init__(self, username, engine, dbpath, ccm_addr, close_on_exit=True):
       
   726         AbstractSession.__init__(self, username, engine, dbpath, ccm_addr)
       
   727         self._execute_lock = threading.Lock()
       
   728         self.close_on_exit = close_on_exit
       
   729 
       
   730     @staticmethod
       
   731     def start(username, password, engine, dbpath, timeout=300):
       
   732         """start the session"""
       
   733         if username == None:
       
   734             raise CCMException('username is not valid')
       
   735         if password == None:
       
   736             raise CCMException('password is not valid')
       
   737         if CCM_BIN == None:
       
   738             raise CCMException("Could not find CM/Synergy executable in the path.")
       
   739         command = "%s start -m -q -nogui -n %s -pw %s -h %s -d %s" % \
       
   740                     (CCM_BIN, username, password, engine, dbpath)
       
   741         _logger.debug('Starting new session:' + command.replace(password, "***"))
       
   742         (result, status) = _execute(command, timeout=timeout)
       
   743         if status != 0:
       
   744             raise Exception("Error creating a session: result:\n%s\nCommand: %s" % (result, command.replace(password, "***")))
       
   745         session_addr = result.strip()
       
   746         _logger.debug(session_addr)
       
   747         if not re.match(r'[a-zA-Z0-9_\-.]+:\d+:\d+\.\d+\.\d+\.\d+(:\d+\.\d+\.\d+\.\d+)?', session_addr):
       
   748             raise Exception("Error creating a session: result:\n%s" % result)
       
   749         return Session(username, engine, dbpath, session_addr)        
       
   750             
       
   751     def execute(self, cmdline, result=None):
       
   752         """ Executes a Synergy CLI operation. """
       
   753         if self._session_addr == None:
       
   754             raise CCMException("No Synergy session running")
       
   755         if CCM_BIN == None:
       
   756             raise CCMException("Could not find CM/Synergy executable in the path.")
       
   757         self._execute_lock.acquire()
       
   758         output = ""
       
   759         error = ""
       
   760         try:
       
   761             if result == None:
       
   762                 result = Result(self)
       
   763             if os.sep == '\\':
       
   764                 command = "set CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " %s" % cmdline
       
   765             else:
       
   766                 command = "export CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " %s" % cmdline
       
   767             _logger.debug('Execute > ' + command)
       
   768 
       
   769             if hasattr(result, 'error'):
       
   770                 process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
       
   771                 output = process.stdout.read()
       
   772                 error = process.stderr.read()
       
   773                 result.status = process.returncode
       
   774             else:
       
   775                 process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
       
   776                 output = process.stdout.read()
       
   777                 result.status = process.returncode
       
   778         finally:
       
   779             self._execute_lock.release()
       
   780         result.output = output.strip()
       
   781         if hasattr(result, 'error'):
       
   782             result.error = error.strip()
       
   783         return result
       
   784 
       
   785     def close(self):
       
   786         """ Closes this Synergy session if it was not previously running anyway. """
       
   787         _logger.debug("Closing session %s" % self._session_addr)
       
   788         if self._session_addr != None and self.close_on_exit:
       
   789             _logger.debug("Closing session %s" % self._session_addr)
       
   790             self._execute_lock.acquire()
       
   791             if os.sep == '\\':
       
   792                 command = "set CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " stop"
       
   793             else:
       
   794                 command = "export CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " stop"
       
   795             _logger.debug('Execute > ' + command)
       
   796             pipe = os.popen(command)
       
   797             pipe.close()
       
   798             self._session_addr = None
       
   799             self._execute_lock.release()
       
   800         elif self._session_addr != None and not self.close_on_exit:
       
   801             _logger.debug("Keeping session %s alive." % self._session_addr)
       
   802 
       
   803 
       
   804 class SessionPool(AbstractSession):
       
   805     """ Session that transparently handled several subsession, to easily enable
       
   806         multithreaded application.
       
   807     """
       
   808     def __init__(self, username, password, engine, dbpath, database=None, size=4, opener=None):
       
   809         AbstractSession.__init__(self, username, engine, dbpath, None)
       
   810         self._opener = opener
       
   811         if self._opener is None:
       
   812             self._opener = open_session
       
   813         self._free_sessions = []
       
   814         self._used_sessions = []
       
   815         self._thread_sessions = {}
       
   816         self._pool_lock = threading.Condition()
       
   817         self._lock_pool = False
       
   818         self.__password = password
       
   819         self.__database = database        
       
   820         self.size = size
       
   821     
       
   822     def _set_size(self, size):
       
   823         """ Set the pool size """ 
       
   824         self._pool_lock.acquire()
       
   825         poolsize = len(self._free_sessions) + len(self._used_sessions)
       
   826         if  poolsize > size:
       
   827             to_be_remove = poolsize - size
       
   828             self._lock_pool = True
       
   829             while len(self._free_sessions) < to_be_remove:
       
   830                 self._pool_lock.wait()            
       
   831             for _ in range(to_be_remove):
       
   832                 self._free_sessions.pop().close()
       
   833             self._lock_pool = False
       
   834         else: 
       
   835             for _ in range(size - poolsize):
       
   836                 self._free_sessions.append(self._opener(self.username, self.__password, self.engine, self.dbpath, self.__database, False))
       
   837         self._pool_lock.release()
       
   838 
       
   839     def _get_size(self):
       
   840         """get pool size"""
       
   841         self._pool_lock.acquire()
       
   842         poolsize = len(self._free_sessions) + len(self._used_sessions)
       
   843         self._pool_lock.release()
       
   844         return poolsize
       
   845 
       
   846     size = property (_get_size, _set_size)
       
   847     
       
   848     def execute(self, cmdline, result=None):
       
   849         """ Executing a ccm command on a free session. """        
       
   850         _logger.debug("SessionPool:execute: %s %s" % (cmdline, type(result)))
       
   851         
       
   852         # waiting for a free session
       
   853         self._pool_lock.acquire()        
       
   854         
       
   855         # check for recursion, in that case reallocate the same session,
       
   856         if threading.currentThread() in self._thread_sessions:
       
   857             _logger.debug("Same thread, reusing allocation session.")
       
   858             # release the pool and reuse associated session
       
   859             self._pool_lock.release()
       
   860             return self._thread_sessions[threading.currentThread()].execute(cmdline, result)
       
   861 
       
   862         while len(self._free_sessions)==0 or self._lock_pool:
       
   863             self._pool_lock.wait()
       
   864         session = self._free_sessions.pop(0)
       
   865         self._used_sessions.append(session)
       
   866         self._thread_sessions[threading.currentThread()] = session
       
   867         self._pool_lock.release()
       
   868         
       
   869         # running command
       
   870         try:
       
   871             result = session.execute(cmdline, result)
       
   872         finally:
       
   873             # we can now release the session - anyway
       
   874             self._pool_lock.acquire()
       
   875             self._thread_sessions.pop(threading.currentThread())                
       
   876             self._used_sessions.remove(session)
       
   877             self._free_sessions.append(session)
       
   878             self._pool_lock.notifyAll()
       
   879             self._pool_lock.release()
       
   880         return result
       
   881 
       
   882     def close(self):
       
   883         """ Closing all subsessions. """
       
   884         _logger.debug("Closing session pool sub-sessions")
       
   885         self._lock_pool = True
       
   886         self._pool_lock.acquire()
       
   887         while len(self._used_sessions) > 0:
       
   888             _logger.debug("Waiting to free used sessions.")
       
   889             _logger.debug("Waiting to free used sessions. %s %s" % (len(self._used_sessions), len(self._free_sessions)))            
       
   890             _logger.debug(self._used_sessions)
       
   891             _logger.debug(self._free_sessions)            
       
   892             self._pool_lock.wait()
       
   893         _logger.debug("Closing all free session from the pool.")
       
   894         while len(self._free_sessions) > 0:
       
   895             self._free_sessions.pop().close()
       
   896         self._lock_pool = False
       
   897         self._pool_lock.notifyAll()
       
   898         self._pool_lock.release()
       
   899     
       
   900     def _set_role_internal(self, role):
       
   901         """ Set role on all subsessions. """
       
   902         self._lock_pool = True
       
   903         self._pool_lock.acquire()
       
   904         while len(self._used_sessions)!=0:
       
   905             self._pool_lock.wait()
       
   906             
       
   907         try:
       
   908             for session in self._free_sessions:
       
   909                 session.role = session._set_role(role)
       
   910         finally:                
       
   911             self._lock_pool = False
       
   912             self._pool_lock.notifyAll()
       
   913             self._pool_lock.release()
       
   914 
       
   915 
       
   916 class Query(object):
       
   917     """ This object wrap a synergy query, it takes a query as input as well as the
       
   918     attribute you want as output, and get them translated using the model configuration.
       
   919     e.g 
       
   920     Query(session, "type='task' and release='test/next'", ['objectname', 'task_synopsis'], ['ccmobject', 'string'])
       
   921     
       
   922     This will return a list of hash: [{'objectname': Task(xxx), 'task_synopsis': 'xxx'}, ...]
       
   923     """
       
   924     
       
   925     def __init__(self, session, query, keywords, model, cmd="query"):
       
   926         """ Initialize a Synergy query."""
       
   927         self._session = session
       
   928         self._query = query
       
   929         self._keywords = keywords
       
   930         self._model = model
       
   931         self._cmd = cmd
       
   932         
       
   933     def execute(self):
       
   934         """ Executing the query on the database. """
       
   935         mapper = DataMapperListResult(self._session, '@@@', self._keywords, self._model)        
       
   936         query = "%s %s -u -f \"%s\"" % (self._cmd, self._query, mapper.format())
       
   937         return self._session.execute(query, mapper)
       
   938         
       
   939     
       
   940 
       
   941 class InvalidFourPartNameException(CCMException):
       
   942     """ Badly formed Synergy four-part name. """
       
   943     def __init__(self, fpn = ""):
       
   944         CCMException.__init__(self, fpn)
       
   945 
       
   946 
       
   947 class FourPartName(object):
       
   948     """ This class handle four part name parsing and validation.
       
   949     """
       
   950 
       
   951     def __init__(self, ifpn):
       
   952         """ Create a FourPartName object based on a ifpn string.
       
   953         
       
   954         The string have to match the following patterns:
       
   955         - name-version:type:instance
       
   956         - name:version:releasedef:instance
       
   957         - Task database#id
       
   958         - Folder database#id
       
   959         
       
   960         Anything else is considered as old release string format.
       
   961             
       
   962         """
       
   963         _logger.debug("FourPartName: '%s'", ifpn)
       
   964         fpn = FourPartName.convert(ifpn)
       
   965         result = re.search(r"^(?P<name>.+)-(?P<version>.+?):(?P<type>\S+):(?P<instance>\S+)$", fpn)
       
   966         if result == None:
       
   967             result = re.search(r"^(?P<name>.+):(?P<version>.+?):(?P<type>releasedef):(?P<instance>\S+)$", fpn)            
       
   968             if result == None:
       
   969                 raise InvalidFourPartNameException(fpn)
       
   970         # set all attributes
       
   971         self._name = result.groupdict()['name']
       
   972         self._version = result.groupdict()['version']
       
   973         self._type = result.groupdict()['type']
       
   974         self._instance = result.groupdict()['instance']    
       
   975 
       
   976     def __getname(self):
       
   977         """ Returns the name of the object. """
       
   978         return self._name
       
   979     
       
   980     def __getversion(self):
       
   981         """ Returns the version of the object. """
       
   982         return self._version
       
   983     
       
   984     def __gettype(self):
       
   985         """ Returns the type of the object. """
       
   986         return self._type
       
   987     
       
   988     def __getinstance(self):
       
   989         """ Returns the instance of the object. """
       
   990         return self._instance
       
   991     
       
   992     def __getobjectname(self):
       
   993         """ Returns the objectname of the object. """
       
   994         if (self.type == 'releasedef'):
       
   995             return "%s:%s:%s:%s" % (self.name, self.version, self.type, self.instance)
       
   996         return "%s-%s:%s:%s" % (self.name, self.version, self.type, self.instance)
       
   997     
       
   998     def __str__(self):
       
   999         """ Returns the string representation of the object. """
       
  1000         return self.objectname
       
  1001     
       
  1002     def __repr__(self):
       
  1003         """ Returns the string representation of the python object. """
       
  1004         if (self.type == 'releasedef'):
       
  1005             return "<%s:%s:%s:%s>" % (self.name, self.version, self.type, self.instance)
       
  1006         return "<%s-%s:%s:%s>" % (self.name, self.version, self.type, self.instance)
       
  1007     
       
  1008     def is_same_family(self, ccmobject):
       
  1009         """ Returns True if the ccmobject is part of the same family (=same name, type and instance) as self. """
       
  1010         assert isinstance(ccmobject, FourPartName)
       
  1011         return (self.name == ccmobject.name and self.type == ccmobject.type and self.instance == ccmobject.instance)
       
  1012     
       
  1013     def __getfamily(self):
       
  1014         """get family"""
       
  1015         return "%s:%s:%s" % (self.name, self.type, self.instance)
       
  1016     
       
  1017     def __eq__(self, ccmobject):
       
  1018         """ Returns True if object four parts name are identical. """
       
  1019         if ccmobject == None:
       
  1020             return False
       
  1021         assert isinstance(ccmobject, FourPartName)
       
  1022         return (self.name == ccmobject.name and self.version == ccmobject.version and self.type == ccmobject.type and self.instance == ccmobject.instance)
       
  1023     
       
  1024     def __ne__(self, ccmobject):
       
  1025         """ Returns True if object four parts name are different. """
       
  1026         if ccmobject == None:
       
  1027             return True
       
  1028         assert isinstance(ccmobject, FourPartName)
       
  1029         return (self.name != ccmobject.name or self.version != ccmobject.version or self.type != ccmobject.type or self.instance != ccmobject.instance)
       
  1030     
       
  1031     @staticmethod
       
  1032     def is_valid(fpn):
       
  1033         """ Check if a given string represents a valid four part name.
       
  1034         """        
       
  1035         return (re.match(r"^(.+)-(.+?):(\S+):(\S+)|(.+):(.+?):releasedef:(\S+)$", fpn) != None)
       
  1036     
       
  1037     @staticmethod
       
  1038     def convert(fpn):
       
  1039         """ Update a CCM output string to a valid four part name. This is due to the inconsistent
       
  1040              output of CM/Synergy CLI.
       
  1041         """
       
  1042         fpn = fpn.strip()
       
  1043         if FourPartName.is_valid(fpn):
       
  1044             return fpn
       
  1045         result = re.search(r"^(?P<type>Task|Folder)\s+(?P<instance>\w+)#(?P<id>\d+)$", fpn)
       
  1046         if result != None:
       
  1047             matches = result.groupdict()
       
  1048             if matches["type"] == "Task":
       
  1049                 return "task%s-1:task:%s" % (matches["id"], matches["instance"])
       
  1050             elif matches["type"] == "Folder":
       
  1051                 return "%s-1:folder:%s" % (matches['id'], matches['instance'])
       
  1052         else:
       
  1053             result = re.search(r"^(?P<project>\S+)/(?P<version>\S+)$", fpn)
       
  1054             if result != None:
       
  1055                 matches = result.groupdict()
       
  1056                 return "%s:%s:releasedef:1" % (matches['project'], matches['version'])        
       
  1057             else:
       
  1058                 # Check the name doesn't contains any of the following character: " :-"
       
  1059                 result = re.search(r"^[^\s^:^-]+$", fpn)
       
  1060                 if result != None:
       
  1061                     return "none:%s:releasedef:1" % (fpn)
       
  1062         raise InvalidFourPartNameException(fpn)
       
  1063 
       
  1064     name = property (__getname)
       
  1065     version = property (__getversion)
       
  1066     type = property (__gettype)
       
  1067     instance = property (__getinstance)
       
  1068     objectname = property (__getobjectname)
       
  1069     family = property(__getfamily)
       
  1070                 
       
  1071                 
       
  1072 class CCMObject(FourPartName):
       
  1073     """ Base class for any Synergy object. """
       
  1074                
       
  1075     def __init__(self, session, fpn):
       
  1076         """initialisation"""
       
  1077         FourPartName.__init__(self, fpn)
       
  1078         self._session = session
       
  1079     
       
  1080     def _getsession(self):
       
  1081         """get session"""
       
  1082         return self._session
       
  1083     
       
  1084     session = property(_getsession)
       
  1085     
       
  1086     def exists(self):
       
  1087         """ Check if an the object exists in the database. """
       
  1088         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)
       
  1089     
       
  1090     def __setitem__(self, name, value):
       
  1091         project = ""
       
  1092         if self.type == 'project':
       
  1093             project = "-p"
       
  1094         if value.endswith("\\"):
       
  1095             value += "\\"
       
  1096         result = self._session.execute("attribute -modify \"%s\" -v \"%s\" %s \"%s\"" % (name, value, project, self))
       
  1097         if result.status != 0 and result.status != None:
       
  1098             raise CCMException("Error modifying '%s' attribute. Result: '%s'" % (name, result.output), result)
       
  1099         
       
  1100     def __getitem__(self, name):
       
  1101         """ Provides access to Synergy object attributes through the dictionary
       
  1102         item interface.
       
  1103         """
       
  1104         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))
       
  1105         if result.status != 0 and result.status != None:
       
  1106             raise CCMException("Error retrieving '%s' attribute. Result: '%s'" % (name, result.output), result)
       
  1107         if len(result.error.strip()) > 0:
       
  1108             raise CCMException("Error retrieving '%s' attribute. Reason: '%s'" % (name, result.error), result)
       
  1109         if result.output.strip() == "<void>":
       
  1110             return None
       
  1111         return result.output.strip()
       
  1112     
       
  1113     def create_attribute(self, name, type_, value=None):
       
  1114         """create attribute"""
       
  1115         if name in self.keys():
       
  1116             raise CCMException("Attribute '%s' already exist." % (name))
       
  1117         args = ""
       
  1118         proj_arg = ""
       
  1119         if value != None:
       
  1120             args += " -value \"%s\"" % value
       
  1121         if self.type == "project":
       
  1122             proj_arg = "-p"
       
  1123         result = self._session.execute("attribute -create \"%s\" -type \"%s\" %s %s \"%s\"" % (name, type_, args, proj_arg, self.objectname))
       
  1124         if result.status != 0 and result.status != None:
       
  1125             raise CCMException("Error creating '%s' attribute. Result: '%s'" % (name, result.output), result)
       
  1126         
       
  1127     def keys(self):
       
  1128         """ The list of supported Synergy attributes. """
       
  1129         result = self._session.execute("attribute -la \"%s\"" % self, AttributeNameListResult(self._session))
       
  1130         return result.output
       
  1131     
       
  1132     def is_predecessor_of(self, obj):
       
  1133         """is predecessor of returns boolean"""
       
  1134         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))
       
  1135         if len(result.output):
       
  1136             return True
       
  1137         return False
       
  1138         
       
  1139     def predecessors(self):
       
  1140         """predecessors"""
       
  1141         result = self._session.execute("query \"is_predecessor_of('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
       
  1142         return result.output
       
  1143 
       
  1144     def successors(self):
       
  1145         """successors"""
       
  1146         result = self._session.execute("query \"is_successor_of('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
       
  1147         return result.output
       
  1148 
       
  1149     def is_recursive_predecessor_of(self, obj):
       
  1150         """is_recursive_predecessor_of returns boolean"""
       
  1151         result = self._session.execute("query \"has_predecessor('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
       
  1152         for sesh in result.output:
       
  1153             if sesh == obj:
       
  1154                 return True
       
  1155         for sesh in result.output:
       
  1156             if sesh.is_recursive_predecessor_of(obj):
       
  1157                 return True
       
  1158         return False
       
  1159 
       
  1160     def is_recursive_predecessor_of_fast(self, obj):
       
  1161         """ Fast implementation of the recursive is_predecessor_of method. """
       
  1162         input_objects = [self]
       
  1163         while len(input_objects) > 0:
       
  1164             query = " or ".join(["has_predecessor('%s')" % x for x in input_objects])
       
  1165             result = self._session.execute("query \"query\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
       
  1166             for sesh in result.output:
       
  1167                 if sesh == obj:
       
  1168                     return True
       
  1169         return False
       
  1170 
       
  1171     def is_recursive_sucessor_of(self, obj):
       
  1172         """is_recursive_sucessor_of returns boolean"""
       
  1173         result = self._session.execute("query \"has_successor('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
       
  1174         for sesh in result.output:
       
  1175             if sesh == obj:
       
  1176                 return True
       
  1177         for sesh in result.output:
       
  1178             if sesh.is_recursive_sucessor_of(obj):
       
  1179                 return True
       
  1180         return False
       
  1181 
       
  1182     def is_recursive_successor_of_fast(self, obj):
       
  1183         """ Fast implementation of the recursive is_successor_of method. """
       
  1184         input_objects = [self]
       
  1185         while len(input_objects) > 0:
       
  1186             query = " or ".join(["has_successor('%s')" % x for x in input_objects])
       
  1187             result = self._session.execute("query \"query\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
       
  1188             for sesh in result.output:
       
  1189                 if sesh == obj:
       
  1190                     return True
       
  1191         return False
       
  1192     
       
  1193     def relate(self, ccm_object):
       
  1194         """relate"""
       
  1195         result = self._session.execute("relate -name successor -from \"%s\" -to \"%s\"" % self, ccm_object, Result(self._session))
       
  1196         if result.status != None and result.status != 0:
       
  1197             raise CCMException("Error relating objects %s to %s\n%s" % (self, ccm_object, result.output))
       
  1198         
       
  1199     def finduse(self):
       
  1200         """ Tries to find where an object is used. """
       
  1201         result = self._session.execute("finduse \"%s\"" % self, FinduseResult(self))
       
  1202         return result.output
       
  1203     
       
  1204     def delete(self, recurse=False):
       
  1205         """ Delete a synergy project. """
       
  1206         args = ""
       
  1207         if recurse:
       
  1208             args = args + " -recurse"
       
  1209         parg = ""
       
  1210         if self.type == "project":
       
  1211             parg = "-project"
       
  1212         result = self._session.execute("delete %s %s \"%s\"" % (args, parg, self))
       
  1213         if result.status != 0 and result.status != None:
       
  1214             raise CCMException("An error occurred while deleting object %s (error status: %s)\n%s" % (self, result.status, result.output), result)
       
  1215         return result
       
  1216 
       
  1217         
       
  1218 class File(CCMObject):
       
  1219     """ Wrapper for any Synergy file object """
       
  1220     
       
  1221     def __init__(self, session, fpn):
       
  1222         CCMObject.__init__(self, session, fpn)
       
  1223     
       
  1224     def content(self):
       
  1225         """content"""
       
  1226         result = self._session.execute("cat \"%s\"" % self)
       
  1227         return result.output
       
  1228     
       
  1229     def to_file(self, path):
       
  1230         """content to file"""
       
  1231         if os.path.exists(path):
       
  1232             _logger.error("Error file %s already exists" % path)
       
  1233         if not os.path.exists(os.path.dirname(path)):
       
  1234             os.makedirs(os.path.dirname(path))
       
  1235         # Content to file        
       
  1236         result = self._session.execute("cat \"%s\" > \"%s\"" % (self, os.path.normpath(path)))
       
  1237         if result.status != 0 and result.status != None:
       
  1238             raise CCMException("Error retrieving content from object %s in %s (error status: %s)\n%s" % (self, path, result.status, result.output), result)
       
  1239     
       
  1240     def merge(self, ccm_object, task):
       
  1241         """merge"""
       
  1242         assert ccm_object != None, "object must be defined."
       
  1243         assert task != None, "task must be defined."
       
  1244         assert task.type == "task", "task parameter must be of 'task' type."
       
  1245         result = self._session.execute("merge -task %s \"%s\" \"%s\"" % (task['displayname'], self, ccm_object))
       
  1246         
       
  1247         validity = 0
       
  1248         for line in result.output.splitlines():
       
  1249             if re.match(r"Merge Source completed successfully\.", line):
       
  1250                 validity = 2
       
  1251             elif re.match(r"Warning: Merge Source warning. \(overlaps during merge\)\.", line):
       
  1252                 validity = 1
       
  1253             else:                
       
  1254                 result = re.match(r"Associated object\s+(?P<object>.+)\s+with task", line)
       
  1255                 if result != None:
       
  1256                     return (self._session.create(result.groupdict()['object']), validity)
       
  1257                     
       
  1258         raise CCMException("Error during merge operation.\n" + result.output, result)
       
  1259 
       
  1260     def checkin(self, state, comment=None):
       
  1261         """checkin"""
       
  1262         if comment != None:
       
  1263             comment = "-c \"%s\"" % comment
       
  1264         else:
       
  1265             comment = "-nc"
       
  1266         result = self._session.execute("checkin -s \"%s\" %s \"%s\" " % (state, comment, self))
       
  1267         for line in result.output.splitlines():
       
  1268             _logger.debug(line)
       
  1269             _logger.debug(r"Checked\s+in\s+'.+'\s+to\s+'%s'" % state)
       
  1270             if re.match(r"Checked\s+in\s+'.+'\s+to\s+'%s'" % state, line) != None:
       
  1271                 return
       
  1272         raise CCMException("Error checking in object %s,\n%s" % (self, result.output), result)
       
  1273         
       
  1274 
       
  1275 class Project(CCMObject):
       
  1276     """ Wrapper class for Synergy project object. """
       
  1277     
       
  1278     def __init__(self, session, fpn):
       
  1279         CCMObject.__init__(self, session, fpn)
       
  1280         self._release = None
       
  1281         self._baseline = None
       
  1282 
       
  1283     def _gettasks(self):
       
  1284         """gettasks"""
       
  1285         result = self._session.execute("rp -show tasks \"%s\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
       
  1286         return result.output
       
  1287 
       
  1288     def add_task(self, task):
       
  1289         """ Add a task to the update properties. """
       
  1290         result = self._session.execute("up -add -task %s \"%s\"" % (task['displayname'], self.objectname))
       
  1291         if result.status != None and result.status != 0:
       
  1292             raise CCMException("Error adding task %s to project '%s'\n%s" % (task, self, result.output))
       
  1293         
       
  1294     def remove_task(self, task):
       
  1295         """ Remove a task to the update properties. """
       
  1296         result = self._session.execute("up -remove -task %s \"%s\"" % (task['displayname'], self.objectname))
       
  1297         if result.status != None and result.status != 0:
       
  1298             raise CCMException("Error removing task %s from project '%s'\n%s" % (task, self, result.output))
       
  1299 
       
  1300     def add_folder(self, folder):
       
  1301         """ Add a folder to the update properties. """
       
  1302         result = self._session.execute("up -add -folder %s \"%s\"" % (folder['displayname'], self.objectname))
       
  1303         if result.status != None and result.status != 0:
       
  1304             raise CCMException("Error adding folder %s to project '%s'\n%s" % (folder, self, result.output))
       
  1305         
       
  1306     def remove_folder(self, folder):
       
  1307         """ Remove a folder to the update properties. """
       
  1308         result = self._session.execute("up -remove -folder %s \"%s\"" % (folder['displayname'], self.objectname))
       
  1309         if result.status != None and result.status != 0:
       
  1310             raise CCMException("Error removing folder %s to project '%s'\n%s" % (folder, self, result.output))
       
  1311     
       
  1312     def _getfolders(self):
       
  1313         """ Wrapper method to return the folder list from the update properties - please use the folders attribute to access it. """
       
  1314         result = self._session.execute("up -show folders \"%s\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
       
  1315         return result.output
       
  1316         
       
  1317     def _getsubprojects(self):
       
  1318         """ Wrapper method to return the subprojects list - please use the subprojects attribute to access it. """
       
  1319         result = self._session.execute("query -t project \"recursive_is_member_of('%s', none)\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
       
  1320         return result.output
       
  1321     
       
  1322     def get_members(self, recursive=False, **kargs):
       
  1323         """get members"""
       
  1324         query = "is_member_of('%s')" % self.objectname
       
  1325         if recursive:
       
  1326             query = "recursive_is_member_of('%s', none)" % self.objectname
       
  1327         for key in kargs.keys():
       
  1328             query += " and %s='%s'" % (key, kargs[key])
       
  1329         result = self._session.execute("query \"%s\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
       
  1330         return result.output
       
  1331         
       
  1332     def _getrelease(self):
       
  1333         """ Get the release of the current object. Returns a Releasedef object. """
       
  1334         self._release = Releasedef(self._session, self['release'])
       
  1335         return self._release
       
  1336 
       
  1337     def _setrelease(self, release):
       
  1338         """ Set the release of the current object. """
       
  1339         self['release'] = release['displayname']
       
  1340     
       
  1341     def refresh(self):
       
  1342         """ Refresh project update properties. """
       
  1343         result = self._session.execute("up -refresh \"%s\"" % self.objectname, UpdatePropertiesRefreshResult(self._session))
       
  1344         return result.output
       
  1345     
       
  1346     def _getbaseline(self):
       
  1347         """ Get the baseline of the current project. """
       
  1348         if self._baseline == None:
       
  1349             result = self._session.execute("up -show baseline_project \"%s\" -f \"%%displayname\" -u" % self.objectname)
       
  1350             if result.output.strip().endswith('does not have a baseline project.'):
       
  1351                 return None
       
  1352             self._baseline = self._session.create(result.output)
       
  1353         _logger.debug('baseline: %s' % self._baseline)
       
  1354         return self._baseline
       
  1355     
       
  1356     def set_baseline(self, baseline, recurse=False):
       
  1357         """ Set project baseline. raise a CCMException in case or error. """
       
  1358         args = ""
       
  1359         if recurse:
       
  1360             args += " -r"
       
  1361         self._baseline = None
       
  1362         result = self._session.execute("up -mb \"%s\" %s \"%s\"" % (baseline, args, self.objectname))
       
  1363         if result.status != None and result.status != 0:
       
  1364             raise CCMException("Error setting basline of project '%s'\n%s" % (self.objectname, result.output))
       
  1365 
       
  1366     def set_update_method(self, name, recurse = False):
       
  1367         """ Set the update method for the project (and subproject if recurse is True). """
       
  1368         assert name != None, "name must not be None."
       
  1369         assert len(name) > 0, "name must not be an empty string."
       
  1370         args = "-ru %s" % name
       
  1371         if recurse:
       
  1372             args += " -r"
       
  1373         result = self._session.execute("up %s \"%s\"" % (args, self))
       
  1374         if result.status != None and result.status != 0:
       
  1375             raise CCMException("Error setting reconfigure properties to %s for project '%s'\nStatus: %s\n%s" % (name, self.objectname, result.status, result.output))
       
  1376    
       
  1377     def apply_update_properties(self, baseline = True, tasks_and_folders = True, recurse=True):
       
  1378         """ Apply update properties to subprojects. """
       
  1379         args = ""
       
  1380         if not baseline:
       
  1381             args += "-no_baseline"
       
  1382         if not tasks_and_folders:
       
  1383             args += " -no_tasks_and_folders"
       
  1384         if recurse:
       
  1385             args += " -apply_to_subprojs"
       
  1386         result = self._session.execute("rp %s \"%s\"" % (args, self.objectname))
       
  1387         if result.status != None and result.status != 0:
       
  1388             raise CCMException("Error applying update properties to subprojects for '%s'\n%s" % (self.objectname, result.output))
       
  1389     
       
  1390     def root_dir(self):
       
  1391         """ Return the directory attached to a project. """
       
  1392         result = self._session.execute("query \"is_child_of('%s','%s')\" -u -f \"%%objectname\"" % (self.objectname, self.objectname), ObjectListResult(self._session))
       
  1393         return result.output[0]
       
  1394     
       
  1395     def snapshot(self, targetdir, recursive=False):
       
  1396         """ Take a snapshot of the project. """
       
  1397         assert targetdir != None, "targetdir must be defined."
       
  1398         if recursive:
       
  1399             recursive = "-recurse"
       
  1400         else:
       
  1401             recursive = ""
       
  1402         result = self._session.execute("wa_snapshot -path \"%s\"  %s \"%s\"" % (os.path.normpath(targetdir), recursive, self.objectname))
       
  1403         for line in result.output.splitlines():
       
  1404             if re.match(r"^Creation of snapshot work area complete.|Copying to file system complete\.\s*$", line):
       
  1405                 return result.output
       
  1406         raise CCMException("Error creation snapshot of %s,\n%s" % (self.objectname, result.output), result)
       
  1407     
       
  1408     def checkout(self, release, version=None, purpose=None, subprojects=True):
       
  1409         """ Create a checkout of this project. 
       
  1410         
       
  1411         This will only checkout the project in Synergy. It does not create a work area.
       
  1412         
       
  1413         :param release: The Synergy release tag to use.
       
  1414         :param version: The new version to use for the project. This is applied to all subprojects.
       
  1415         :param purpose: The purpose of the checkout. Determines automatically the role from the purpose
       
  1416          and switch it automatically (Could be any role from the DB).
       
  1417         """    
       
  1418         assert release != None, "Release object must be defined."
       
  1419         if not release.exists():
       
  1420             raise CCMException("Release '%s' must exist in the database." % release)
       
  1421             
       
  1422         args = ''
       
  1423         if version != None:
       
  1424             args += '-to "%s"' % version
       
  1425         role = None
       
  1426         if purpose:
       
  1427             #save current role before changing
       
  1428             role = self._session.role
       
  1429 
       
  1430             self._session.role = get_role_for_purpose(self._session, purpose)
       
  1431             
       
  1432             args += " -purpose \"%s\"" % purpose
       
  1433         if subprojects:
       
  1434             args += " -subprojects"
       
  1435         result = self._session.execute("checkout -project \"%s\" -release \"%s\" -no_wa %s" \
       
  1436                                   % (self, release['displayname'], args), ProjectCheckoutResult(self._session, self.objectname))
       
  1437         if not role is  None:
       
  1438             self._session.role = role
       
  1439         if result.project == None:
       
  1440             raise CCMException("Error checking out project %s,\n%s" % (self.objectname, result.output), result)
       
  1441         return result
       
  1442     
       
  1443     def work_area(self, maintain, recursive=None, relative=None, path=None, pst=None, wat=False):
       
  1444         """ Configure the work area. This allow to enable it or disable it, set the path, recursion... """
       
  1445         args = ""
       
  1446         if maintain:
       
  1447             args += "-wa"
       
  1448         else:
       
  1449             args += "-nwa"
       
  1450         # path
       
  1451         if path != None:
       
  1452             args += " -path \"%s\"" % path        
       
  1453         # pst
       
  1454         if pst != None:
       
  1455             args += " -pst \"%s\"" % pst
       
  1456         # relative
       
  1457         if relative != None and relative:
       
  1458             args += " -relative"
       
  1459         elif relative != None and not relative:
       
  1460             args += " -not_relative"
       
  1461         # recursive
       
  1462         if recursive != None and recursive:
       
  1463             args += " -recurse"
       
  1464         elif recursive != None and not recursive:
       
  1465             args += " -no_recurse"        
       
  1466         #wat            
       
  1467         if wat:
       
  1468             args += " -wat"
       
  1469         result = self._session.execute("work_area -project \"%s\" %s" \
       
  1470                                   % (self.objectname, args), Result(self._session))
       
  1471         return result.output
       
  1472         
       
  1473     def update(self, recurse=True, replaceprojects=True, keepgoing=False, result=None):
       
  1474         """ Update the project based on its reconfigure properties. """
       
  1475         args = ""
       
  1476         if recurse:
       
  1477             args += " -r "
       
  1478         if replaceprojects:
       
  1479             args += " -rs "
       
  1480         else:
       
  1481             args += " -ks "
       
  1482         if result == None:
       
  1483             result = UpdateResult(self._session)
       
  1484         result = self._session.execute("update %s -project %s" % (args, self.objectname), result)
       
  1485         if not result.successful and not keepgoing:
       
  1486             raise CCMException("Error updating %s" % (self.objectname), result)
       
  1487         return result
       
  1488     
       
  1489     def reconcile(self, updatewa=True, recurse=True, consideruncontrolled=True, missingwafile=True, report=True):
       
  1490         """ Reconcile the project to force the work area to match the database. """
       
  1491         args = ""
       
  1492         if updatewa:
       
  1493             args += " -update_wa "
       
  1494         if recurse:
       
  1495             args += " -recurse "
       
  1496         if consideruncontrolled:
       
  1497             args += " -consider_uncontrolled "
       
  1498         if missingwafile:
       
  1499             args += " -missing_wa_file "
       
  1500         if report:
       
  1501             args += " -report reconcile.txt "
       
  1502         result = self._session.execute("reconcile %s -project %s" % (args, self.objectname), Result(self._session))
       
  1503         if re.search(r"There are no conflicts in the Work Area", result.output) == None and re.search(r"Reconcile completed", result.output) == None:
       
  1504             raise CCMException("Error reconciling %s,\n%s" % (self.objectname, result.output), result)        
       
  1505         return result.output
       
  1506 
       
  1507     def get_latest_baseline(self, filterstring="*", state="released"):
       
  1508         """get_latest_baseline"""
       
  1509         result = self._session.execute("query -n %s -t project -f \"%%displayname\" -s %s -u -ns \"version smatch'%s'\"" % (self.name, state, filterstring))
       
  1510         lines = result.output.splitlines()
       
  1511         return lines[-1]
       
  1512 
       
  1513     def create_baseline(self, baseline_name, release, baseline_tag, purpose="System Testing", state="published_baseline"):
       
  1514         """create_baseline"""
       
  1515         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))
       
  1516         return result.output
       
  1517     
       
  1518     def sync(self, recurse=False, static=False):
       
  1519         """ Synchronize project content. By default it is not been done recusively. (Not unittested)"""
       
  1520         args = ""
       
  1521         if recurse:
       
  1522             args += " -recurse"
       
  1523         if static:
       
  1524             args += " -static"
       
  1525         result = self._session.execute("sync %s -project \"%s\"" % (args, self.objectname))
       
  1526         if result.status != None and result.status != 0:
       
  1527             raise CCMException("Error during synchronization of %s: %s." % (self.objectname, result.output))
       
  1528         return result.output
       
  1529 
       
  1530     def conflicts(self, recurse=False, tasks=False):
       
  1531         """conflicts"""
       
  1532         args = "-noformat "
       
  1533         if recurse:
       
  1534             args += " -r"
       
  1535         if tasks:
       
  1536             args += " -t"
       
  1537         
       
  1538         result = self._session.execute("conflicts %s  \"%s\"" % (args, self.objectname), ConflictsResult(self._session))
       
  1539         if result.status != None and result.status != 0:
       
  1540             raise CCMException("Error during conflict detection of %s: %s." % (self.objectname, result))
       
  1541         return result
       
  1542     
       
  1543     tasks = property(_gettasks)
       
  1544     folders = property(_getfolders)
       
  1545     subprojects = property(_getsubprojects)
       
  1546     release = property(_getrelease, _setrelease)
       
  1547     baseline = property(_getbaseline, set_baseline)
       
  1548 
       
  1549 
       
  1550 class Dir(CCMObject):
       
  1551     """ Wrapper class for Synergy dir object """
       
  1552     
       
  1553     def __init__(self, session, fpn):
       
  1554         CCMObject.__init__(self, session, fpn)
       
  1555 
       
  1556     def children(self, project):
       
  1557         """children"""
       
  1558         assert(project.type == 'project')
       
  1559         result = self._session.execute("query \"is_child_of('%s','%s')\" -u -f \"%%objectname\"" % (self.objectname, project), ObjectListResult(self._session))
       
  1560         return result.output
       
  1561         
       
  1562 
       
  1563 class Releasedef(CCMObject):
       
  1564     """ Wrapper class for Synergy releasedef object """
       
  1565     
       
  1566     def __init__(self, session, fpn):
       
  1567         CCMObject.__init__(self, session, fpn)
       
  1568     
       
  1569     def _getcomponent(self):
       
  1570         """get component"""
       
  1571         return self.name
       
  1572             
       
  1573     component = property(_getcomponent)
       
  1574 
       
  1575 
       
  1576 class Folder(CCMObject):
       
  1577     """ Wrapper class for Synergy folder object """
       
  1578     
       
  1579     def __init__(self, session, fpn):
       
  1580         CCMObject.__init__(self, session, fpn)
       
  1581 
       
  1582     def _gettasks(self):
       
  1583         """ Accessor for 'tasks' property. """
       
  1584         result = self._session.execute("folder -show tasks \"%s\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
       
  1585         return result.output
       
  1586 
       
  1587     def _getobjects(self):
       
  1588         """get objects"""
       
  1589         result = self._session.execute("folder -show objects \"%s\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
       
  1590         return result.output
       
  1591 
       
  1592     def _getmode(self):
       
  1593         """ Get the mode used by the folder. """
       
  1594         result = self._session.execute("folder -show mode \"%s\"" % self.objectname)
       
  1595         return result.output.strip()
       
  1596 
       
  1597     def _getquery(self):
       
  1598         """ Get the query that populate the folder. """
       
  1599         if self.mode.lower() == "query":
       
  1600             result = self._session.execute("folder -show query \"%s\"" % self.objectname)
       
  1601             return result.output.strip()
       
  1602         else:
       
  1603             raise CCMException("%s is not a query base folder." % (self.objectname))
       
  1604     
       
  1605     def _getdescription(self):
       
  1606         """ Get the description associated with the folder. """
       
  1607         result = self._session.execute("query -t folder -n %s -i %s -u -f \"%%description\"" % (self.name, self.instance))
       
  1608         return result.output.strip()
       
  1609 
       
  1610     def remove(self, task):
       
  1611         """ Remove task from this folder. """
       
  1612         result = self._session.execute("folder -m \"%s\" -remove_task \"%s\"" % (self.objectname, task.objectname))
       
  1613         if result.status != None and result.status != 0:
       
  1614             raise CCMException("Error removing task %s from %s: %s." % (task.objectname, self.objectname, result.output))
       
  1615 
       
  1616     def update(self):
       
  1617         """update"""
       
  1618         result = self._session.execute("folder -m -update -f \"%%objectname\"" % self.objectname)
       
  1619         if result.status != None and result.status != 0:
       
  1620             raise CCMException("Error updating the folder content %s: %s." % (self.objectname, result.output))
       
  1621         
       
  1622     def append(self, task):
       
  1623         """ Associate an object to a task """
       
  1624         class AddTaskException(CCMException):
       
  1625             """add task exception"""
       
  1626             def __init__(self, reason, task, result):
       
  1627                 CCMException.__init__(self, reason, result)
       
  1628                 self.task = task
       
  1629         
       
  1630         result = self._session.execute("folder -m -at \"%s\" \"%s\"" % (task.objectname, self.objectname))
       
  1631         if re.search(r"(Added 1 task to)|(is already in folder)", result.output, re.M) is None:
       
  1632             raise AddTaskException(result.output, result, task)
       
  1633     
       
  1634     def copy(self, existing_folder):
       
  1635         """ Copy the contents of existing_folder into this folder.
       
  1636         
       
  1637         This appends to the destination folder by default.
       
  1638         
       
  1639         :param existing_folder: The destination Folder object.
       
  1640         """
       
  1641         result = self._session.execute("folder -copy %s -existing %s -append" % (self.objectname, existing_folder), FolderCopyResult(self._session))
       
  1642         return result.output
       
  1643         
       
  1644     objects = property(_getobjects)
       
  1645     tasks = property(_gettasks)
       
  1646     mode = property(_getmode)
       
  1647     query = property(_getquery)
       
  1648     is_query_based = property(lambda x: x.mode.lower() == "query")
       
  1649     description = property(_getdescription)
       
  1650 
       
  1651 
       
  1652 class Task(CCMObject):
       
  1653     """ Wrapper class for Synergy task object """
       
  1654     
       
  1655     def __init__(self, session, fpn):
       
  1656         CCMObject.__init__(self, session, fpn)
       
  1657         self.__unicode_str_text = None
       
  1658 
       
  1659     def _getobjects(self):
       
  1660         """get objects"""
       
  1661         result = self._session.execute("task -show objects \"%s\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
       
  1662         return result.output
       
  1663     
       
  1664     def append(self, ccm_object):
       
  1665         """ Associate an object to a task """
       
  1666         class AddObjectException(CCMException):
       
  1667             """add object exception"""
       
  1668             def __init__(self, comment, ccm_object):
       
  1669                 CCMException.__init__(self, comment)
       
  1670                 self.ccm_object = ccm_object
       
  1671         
       
  1672         result = self._session.execute("task -associate \"%s\" -object \"%s\"" % (self.objectname, ccm_object.objectname))
       
  1673         if not re.match(r"Associated object .+ with task .*\.", result.output, re.M):
       
  1674             raise AddObjectException(result.output)
       
  1675 
       
  1676     def assign(self, username):
       
  1677         """assign"""
       
  1678         result = self._session.execute("task -modify \"%s\" -resolver %s" % (self.objectname, username))
       
  1679         if not re.match(r"Changed resolver of task", result.output, re.M):
       
  1680             raise CCMException("Error assigning task to user '%s',\n%s" % (username, result.output), result)
       
  1681         
       
  1682     def _getsynopsis(self):
       
  1683         """get synopsis"""
       
  1684         return self['task_synopsis']
       
  1685         
       
  1686     @staticmethod
       
  1687     def create(session, release_tag, synopsis=""):
       
  1688         """create"""
       
  1689         assert release_tag.type == "releasedef", "release_tag must be a CCM object wrapper of releasedef type"
       
  1690         result = session.execute("task -create -synopsis \"%s\" -release \"%s\"" % (synopsis, release_tag['displayname']), CreateNewTaskResult(session))
       
  1691         return result.output
       
  1692         
       
  1693     objects = property(_getobjects)
       
  1694     
       
  1695     def __unicode__(self):
       
  1696         # TODO: use optimised query that makes only 1 ccm query with suitable format
       
  1697         if self.__unicode_str_text == None:
       
  1698             self.__unicode_str_text = u'%s: %s' % (self['displayname'], self['task_synopsis'])
       
  1699         return self.__unicode_str_text
       
  1700         
       
  1701     def __str__(self):
       
  1702         return self.__unicode__().encode('ascii', 'replace')
       
  1703     
       
  1704     def get_release_tag(self):
       
  1705         """ Get task release. Use release property!"""
       
  1706         result = self._session.execute("attribute -show release \"%s\"" % (self.objectname), Result(self._session))
       
  1707         return result.output
       
  1708     
       
  1709     def set_release_tag(self, release_tag):
       
  1710         """ Set task release. Use release property!"""
       
  1711         result = self._session.execute("attribute -modify release -value \"%s\" \"%s\"" % (release_tag, self.objectname), Result(self._session))
       
  1712         return result.output
       
  1713 
       
  1714     release = property(get_release_tag, set_release_tag)
       
  1715 
       
  1716 class UpdateTemplate:
       
  1717     """ Allow to access Update Template property using Release and Purpose. """
       
  1718     def __init__(self, releasedef, purpose):
       
  1719         assert(releasedef != None)
       
  1720         assert(purpose != None)
       
  1721         self._releasedef = releasedef
       
  1722         self._purpose = purpose
       
  1723         
       
  1724     def objectname(self):
       
  1725         """ Return the objectname representing this virtual object. """
       
  1726         return "%s:%s" % (self._releasedef['displayname'], self._purpose)
       
  1727 
       
  1728     def baseline_projects(self):
       
  1729         """ Query all projects for this UpdateTemplate. """
       
  1730         result = self._releasedef.session.execute("ut -sh baseline_projects \"%s\"" % self.objectname(), ObjectListResult(self._releasedef.session))
       
  1731         print result.output
       
  1732         return result.output
       
  1733 
       
  1734     def information(self):
       
  1735         """ Query all projects for this UpdateTemplate. """
       
  1736         result = self._releasedef.session.execute("ut -sh information \"%s\"" % self.objectname(), UpdateTemplateInformation(self._releasedef.session))
       
  1737         print result.output
       
  1738         return result.output
       
  1739 
       
  1740     def baseline_selection_mode(self):
       
  1741         """ The current Baseline selection mode """
       
  1742         result = self._releasedef.session.execute("ut -sh bsm \"%s\"" % self.objectname())
       
  1743         print result.output.strip()
       
  1744         return result.output.strip()
       
  1745 
       
  1746 
       
  1747 def read_ccmwaid_info(filename):
       
  1748     """ Read data from a ccmwaid file. This method is an helper to retreive a project from a physical location. """
       
  1749     ccmwaid = open(filename, 'r')
       
  1750     # first line: database
       
  1751     dbpath = os.path.dirname(ccmwaid.readline().strip())
       
  1752     database = os.path.basename(dbpath)
       
  1753     # 2nd line should be a timestamp
       
  1754     ccmwaid.readline().strip()
       
  1755     # 3rd line is the objectname
       
  1756     objectref = ccmwaid.readline().strip()
       
  1757     ccmwaid.close()    
       
  1758     return {'dbpath': dbpath, 'database': database, 'objectname': objectref}
       
  1759 
       
  1760 def create_project_from_path(session, path):
       
  1761     """ Uses the (_|.)ccmwaid.inf file to create a Project object. """
       
  1762     ccmwaid = ".ccmwaid.inf"
       
  1763     if os.name == 'nt':
       
  1764         ccmwaid = "_ccmwaid.inf"
       
  1765         
       
  1766     if (not os.path.exists(path + "/" + ccmwaid)):
       
  1767         return None    
       
  1768     result = read_ccmwaid_info(path + "/" + ccmwaid)
       
  1769     
       
  1770     return session.create(result['objectname'])
       
  1771 
       
  1772 
       
  1773 def open_session(username=None, password=None, engine=None, dbpath=None, database=None, reuse=True):
       
  1774     """Provides a Session object.
       
  1775     
       
  1776     Attempts to return a Session, based either on existing Synergy
       
  1777     sessions or by creating a new one.
       
  1778     
       
  1779     - If a .netrc file can be found on the user's personal drive,
       
  1780       that will be read to obtain Synergy login information if it 
       
  1781       is defined there. This will be used to fill in any missing 
       
  1782       parameters not passed in the call to open_session().
       
  1783       
       
  1784       The format of the .netrc file entries should be:
       
  1785       
       
  1786       machine synergy login USERNAME password foobar account DATABASE_PATH@SERVER
       
  1787       
       
  1788       If the details refer to a specific database, the machine can be the database name,
       
  1789       instead of "synergy".
       
  1790     - If an existing session is running that matches the supplied
       
  1791       parameters, it will reuse that.
       
  1792     
       
  1793     """
       
  1794     # See if a .netrc file can be used
       
  1795     if CCM_BIN == None:
       
  1796         raise CCMException("Could not find CM/Synergy executable in the path.")
       
  1797     if password == None or username == None or engine == None or dbpath == None:
       
  1798         if os.sep == '\\':
       
  1799             os.environ['HOME'] = "H:" + os.sep
       
  1800         _logger.debug('Opening .netrc file')
       
  1801         try:
       
  1802             netrc_file = netrc.netrc()
       
  1803             netrc_info = None
       
  1804             # If settings for a specific database 
       
  1805             if database != None:
       
  1806                 netrc_info = netrc_file.authenticators(database)
       
  1807 
       
  1808             # if not found just try generic one
       
  1809             if netrc_info == None:
       
  1810                 netrc_info = netrc_file.authenticators('synergy')
       
  1811                 
       
  1812             if netrc_info != None:
       
  1813                 (n_username, n_account, n_password) = netrc_info
       
  1814                 if username == None:
       
  1815                     username = n_username
       
  1816                 if password == None:
       
  1817                     password = n_password
       
  1818                 if n_account != None:
       
  1819                     (n_dbpath, n_engine) = n_account.split('@')
       
  1820                     if dbpath == None and n_dbpath is not None:
       
  1821                         _logger.info('Database path set using .netrc (%s)' % n_dbpath)
       
  1822                         dbpath = n_dbpath
       
  1823                     if engine == None and n_engine is not None:
       
  1824                         _logger.info('Database engine set using .netrc (%s)' % n_engine)
       
  1825                         engine = n_engine
       
  1826         except IOError:
       
  1827             _logger.debug('Error accessing .netrc file')
       
  1828 
       
  1829     # last chance...
       
  1830     if username == None:
       
  1831         username = os.environ['USERNAME']
       
  1832 
       
  1833     # looking for dbpath using GSCM database
       
  1834     if dbpath == None and database != None:
       
  1835         _logger.info('Database path set using the GSCM database.')
       
  1836         dbpath = nokia.gscm.get_db_path(database)
       
  1837 
       
  1838     # looking for engine host using GSCM database
       
  1839     if engine == None and database != None:
       
  1840         _logger.info('Database engine set using the GSCM database.')
       
  1841         engine = nokia.gscm.get_engine_host(database)
       
  1842 
       
  1843     _sessions = []
       
  1844     # See if any currently running sessions can be used, only if no password submitted, else use a brand new session!
       
  1845     if password == None and reuse:
       
  1846         _logger.debug('Querying for existing Synergy sessions')
       
  1847         command = "%s status" % (CCM_BIN)
       
  1848         pipe = os.popen(command, 'r')
       
  1849         result = pipe.read()
       
  1850         pipe.close()
       
  1851         _logger.debug('ccm status result: ' + result)
       
  1852         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):
       
  1853             dictionary = match.groupdict()
       
  1854             _logger.debug(dictionary['ccmaddr'])
       
  1855             _logger.debug(socket.gethostname())
       
  1856             _logger.debug(dictionary['current_session'])
       
  1857             if dictionary['ccmaddr'].lower().startswith(socket.gethostname().lower()):
       
  1858                 # These session objects should not close the session on deletion,
       
  1859                 # because they did not initially create the session
       
  1860                 existing_session = Session(username, engine, dictionary['dbpath'], dictionary['ccmaddr'], close_on_exit=False)
       
  1861                 _logger.debug('Existing session found: %s' % existing_session)
       
  1862                 _sessions.append(existing_session)
       
  1863         # looking for session using dbpath
       
  1864         for session in _sessions:
       
  1865             if session.dbpath == dbpath:
       
  1866                 return session
       
  1867     else:
       
  1868         # looking for router address using GSCM database
       
  1869         router_address = None
       
  1870         if database == None and dbpath != None:
       
  1871             database = os.path.basename(dbpath)
       
  1872         
       
  1873         lock = fileutils.Lock(CCM_SESSION_LOCK)
       
  1874         try:
       
  1875             lock.lock(wait=True)
       
  1876             # if we have the database name we can switch to the correct Synergy router
       
  1877             if database != None:
       
  1878                 _logger.info('Getting router address.')
       
  1879                 router_address = nokia.gscm.get_router_address(database)
       
  1880                 if os.sep == '\\' and router_address != None:
       
  1881                     routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), 'r')
       
  1882                     current_router = routerfile.read().strip()
       
  1883                     routerfile.close()
       
  1884                     if current_router != router_address.strip():
       
  1885                         _logger.info('Updating %s' % (os.path.normpath(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"))))
       
  1886                         routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), "w+")
       
  1887                         routerfile.write("%s\n" % router_address)
       
  1888                         routerfile.close()
       
  1889         
       
  1890             # If no existing sessions were available, start a new one
       
  1891             _logger.info('Opening session.')
       
  1892             new_session = Session.start(username, password, engine, dbpath)
       
  1893             lock.unlock()
       
  1894             return new_session
       
  1895         finally:
       
  1896             lock.unlock()
       
  1897     raise CCMException("Cannot open session for user '%s'" % username)
       
  1898 
       
  1899 
       
  1900 def get_role_for_purpose(session, purpose):
       
  1901     """  return role needed to modify project with checkout for purpose. """
       
  1902     purposes = session.purposes()
       
  1903     if purpose in purposes:
       
  1904         if purposes[purpose]['status'] == 'prep':
       
  1905             return 'build_mgr'
       
  1906     else:
       
  1907         raise CCMException("Could not find purpose '%s' in the database.\n Valid purpose are: %s." % (purpose, ','.join(purposes.keys())))
       
  1908     return 'developer'
       
  1909 
       
  1910 def get_role_for_status(status):
       
  1911     """  return role needed to modify project with a specific status. """
       
  1912     if status == 'prep':
       
  1913         return 'build_mgr'
       
  1914     elif status == 'shared':
       
  1915         return 'developer'
       
  1916     elif status == 'working':
       
  1917         return 'developer'
       
  1918     else:
       
  1919         raise CCMException("Unknow status '%s'" % status)
       
  1920 
       
  1921 def running_sessions(database=None):
       
  1922     """ Return the list of synergy session currently available on the local machine.
       
  1923         If database is given then it tries to update the router address.
       
  1924     """
       
  1925     _logger.debug('Querying for existing Synergy sessions')
       
  1926     if CCM_BIN == None:
       
  1927         raise CCMException("Could not find CM/Synergy executable in the path.")
       
  1928     command = "%s status" % (CCM_BIN)
       
  1929 
       
  1930     lock = fileutils.Lock(CCM_SESSION_LOCK)
       
  1931     result = ""
       
  1932     output = []
       
  1933     try:
       
  1934         # if we have the database name we can switch to the correct Synergy router
       
  1935         if database != None:
       
  1936             lock.lock(wait=True)
       
  1937             _logger.info('Updating router address.')
       
  1938             router_address = nokia.gscm.get_router_address(database)
       
  1939             if os.sep == '\\' and router_address != None:
       
  1940                 routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), 'r')
       
  1941                 current_router = routerfile.read().strip()
       
  1942                 routerfile.close()
       
  1943                 if current_router != router_address.strip():
       
  1944                     _logger.info('Updating %s' % (os.path.normpath(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"))))
       
  1945                     routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), "w+")
       
  1946                     routerfile.write("%s\n" % router_address)
       
  1947                     routerfile.close()
       
  1948 
       
  1949         _logger.debug('Command: ' + command)
       
  1950         (result, status) = _execute(command)
       
  1951         if database != None:
       
  1952             lock.unlock()
       
  1953         if (status != 0):
       
  1954             raise CCMException("Ccm status execution returned an error.")
       
  1955         _logger.debug('ccm status result: ' + result)
       
  1956         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):
       
  1957             data = match.groupdict()
       
  1958             _logger.debug(data['ccmaddr'])
       
  1959             _logger.debug(socket.gethostname())
       
  1960             _logger.debug(data['current_session'])
       
  1961             if data['ccmaddr'].lower().startswith(socket.gethostname().lower()):
       
  1962                 # These session objects should not close the session on deletion,
       
  1963                 # because they did not initially create the session
       
  1964                 existing_session = Session(None, None, data['dbpath'], data['ccmaddr'], close_on_exit=False)
       
  1965                 _logger.debug('Existing session found: %s' % existing_session)
       
  1966                 output.append(existing_session)
       
  1967     finally:
       
  1968         if database != None:
       
  1969             lock.unlock()
       
  1970     return  output
       
  1971 
       
  1972 def session_exists(sessionid, database=None):
       
  1973     """determine if session exists"""
       
  1974     for session in running_sessions(database=database):
       
  1975         _logger.debug(session.addr() + "==" + sessionid + "?")
       
  1976         if session.addr() == sessionid:
       
  1977             return True
       
  1978     return False
       
  1979 
       
  1980 # The location of the ccm binary must be located to know where the _router.adr file is, to support
       
  1981 # switching databases.
       
  1982 CCM_BIN = fileutils.which("ccm")
       
  1983 if os.sep == '\\':
       
  1984     CCM_BIN = fileutils.which("ccm.exe")