Package ccm |
[hide private]
[frames] | no frames]

# Source Code for Package ccm

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