#
# buildservice.py - Buildservice API
#

# Copyright (C) 2010, 2011, 2012, 2013, 2014 Intel, Inc.
#
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of the GNU General Public License
#    as published by the Free Software Foundation; version 2 of the License.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""Buildservice API with osc API invoked"""

import base64
import os
import shutil
import urllib2
import json
import cgi
import xml.etree.cElementTree as ElementTree
from time import sleep

from gitbuildsys.errors import ObsError
from gitbuildsys.oscapi import OSC
from gitbuildsys.utils import Temp

from common.utils import retry, xml_to_obj

from osc import conf, core


def encode_passwd(passwd):
    '''encode passwd by bz2 and base64'''
    return base64.b64encode(passwd.encode('bz2'))

OSCRC_TEMPLATE = """[general]
apiurl = %(apiurl)s
plaintext_passwd=0
use_keyring=0
http_debug = %(http_debug)s
debug = %(debug)s
gnome_keyring=0
[%(apiurl)s]
sslcertck=0
user=%(user)s
passx=%(passwdx)s
"""

OSCRC_MULTI_TEMPLATE = """[general]
apiurl = %(apiurl)s
plaintext_passwd=0
use_keyring=0
http_debug = %(http_debug)s
debug = %(debug)s
gnome_keyring=0
[%(apiurl)s]
sslcertck=0
user=%(user)s
passx=%(passwdx)s
[%(remote_apiurl)s]
sslcertck=0
user=%(remote_user)s
passx=%(remote_passwdx)s

"""


class BuildService(OSC):
    # a class can only contain one _init_ function
    """Interface to Build Service API"""
    '''
    def __init__(self, apiurl, apiuser, apipasswd):
        oscrc = OSCRC_TEMPLATE % {
            "http_debug": 0,
            "debug": 0,
            "apiurl": apiurl,
            "user": apiuser,
            "passwdx": encode_passwd(apipasswd)}

        self.apiurl = apiurl
        tmpf = Temp(prefix='.oscrc', content=oscrc)
        self.oscrcpath = tmpf.path
        OSC.__init__(self, apiurl, self.oscrcpath)
    '''

    def __init__(self, apiurl, apiuser, apipasswd, \
                       remote_apiurl=None, remote_apiuser=None, remote_apipasswd=None):
        if not remote_apiurl and not remote_apiuser and not remote_apipasswd:
            oscrc = OSCRC_TEMPLATE % {
                "http_debug": 0,
                "debug": 0,
                "apiurl": apiurl,
                "user": apiuser,
                "passwdx": encode_passwd(apipasswd)}
        else:
            oscrc = OSCRC_MULTI_TEMPLATE % {
                "http_debug": 0,
                "debug": 0,
                "apiurl": apiurl,
                "user": apiuser,
                "passwdx": encode_passwd(apipasswd),
                "remote_apiurl": remote_apiurl,
                "remote_user": remote_apiuser,
                "remote_passwdx": encode_passwd(remote_apipasswd)}
        self.apiurl = apiurl
        self.remote_apiurl = remote_apiurl
        tmpf = Temp(prefix='.oscrc', content=oscrc)
        self.oscrcpath = tmpf.path
        OSC.__init__(self, apiurl, self.oscrcpath)

    def get_src_file_list(self, project, package, revision=None):
        """ get source file list of prj/pac
        """
        return core.meta_get_filelist(self.apiurl, project, package,
                expand=True, revision=revision)

    def get_src_file_list_no_expand(self, project, package, revision=None):
        """ get source file list of prj/pac
        """
        return core.meta_get_filelist(self.apiurl, project, package,
                expand=False, revision=revision)

    def get_src_file_content(self, project, package, path, revision=None):
        """ Cat remote file
        """

        rev = core.show_upstream_xsrcmd5(self.apiurl, project, package,
                revision=revision)
        if rev:
            query = {'rev': rev}
        else:
            query = None

        return self.get_source_path("%s/%s/%s" % (project, package, path),
                                     query).decode('utf8')

    def get_source_path(self, path, query=None):
        """Get remote content of the project/package/file path."""
        url = core.makeurl(self.apiurl, ['source', core.pathname2url(path)],
                          query=query)
        content = ''
        for buf in core.streamfile(url, core.http_GET, core.BUFSIZE):
            content += buf
        return content

    def put_source_path(self, path, data, query=None):
        """Put content to remote project/package/file path."""
        url = core.makeurl(self.apiurl, ['source', core.pathname2url(path)],
                          query=query)
        self.core_http(core.http_PUT, url, data=data)

    def get_sr_str(self, reqid):
        """Get the SR xml string by reqid
        """
        try:
            req = core.get_request(self.apiurl, reqid)
            return req.to_str()
        except (urllib2.URLError, urllib2.HTTPError), err:
            print "can't get the string of SR xml, because %s" % err
            return None

    def get_sr_info(self, reqid):
        """Get some info of SR by reqid
           Currently, We need 'sourceproject',
           'targetproject', 'comment', 'sender'
           If need more, can add other data from the xml_root
           into the function in future.
           Use utils.xml_to_obj to parse the xml, then get the
           data that we want by the xml and node_xml.
           node_xml = {'name': ['child_node/data', 'child_node/data', ....]}
           For example:
           After xml_to_obj, get the result:
           {action:{source:{package:'automotive-message-broker',
                            project:'home:prerelease:Tizen:IVI:
                                     submit:tizen_ivi:20141111.212504'},
                    target:{package:'automotive-message-broker',
                            project:'Tizen:IVI'},
                    type:'submit'},
           state:{comment:"The source project 'home:prerelease:Tizen:
                          IVI:submit:tizen_ivi:20141111.212504' was removed",
                  name:'revoked', when:'2014-11-12T02:10:47',
                  who:'Admin'}}
           if node_xml is {'sourcepackage':['action', 'source', 'package'], \
                       'comment': ['state', 'comment']}
           Use the xml and node_xml, the function will return
           {'sourcepackage': 'automotive-message-broker',
            'sender': 'Admin',
           }
        """
        node_xml = {'sourceproject':['action', 'source', 'project', str], \
            'targetproject':['action', 'target', 'project', str],
            'sourcepackagelist':['action', 'source', 'package', list],
            'targetpackagelist':['action', 'target', 'package', list],
            'comment': ['state', 'comment', str],
            'sender': ['state', 'who', str],
           }
        def _get_node_data(nodes, xml_info):
            """Get node data from xml_info
               nodes = ['child_node/data', 'child_node/data', ....]
            """
            try:
                data = xml_info[nodes[0]]
                ret_data = []
                if not isinstance(data, list):
                    data = [data]
                for d in data:
                    _data = d
                    for node in nodes[1:]:
                        _data = _data[node]
                    ret_data.append(_data)
                return ret_data
            except (AttributeError, ValueError):
                return []

        sr_info = {}
        # get the SR xml string by reqid
        xml_root = self.get_sr_str(reqid)
        if xml_root:
            # parse the SR xml
            xml_info = xml_to_obj(xml_root)
            for name, nodes in node_xml.items():
                if nodes[-1] == list:
                    if name not in sr_info:
                        sr_info[name] = []
                    sr_info[name].extend(_get_node_data(nodes[:-1], xml_info))
                else:
                    sr_info[name] = _get_node_data(nodes[:-1], xml_info)[0]

        return sr_info


    def gen_request_info(self, reqid, show_detail=True):
        """Generate formated diff info for request,
        mainly used by notification mails from BOSS
        """

        def _gen_request_diff():
            """
            Recommanded getter: request_diff can get req diff info even if
            req is accepted/declined
            """
            reqdiff = ''

            try:
                diff = core.request_diff(self.apiurl, reqid)

                try:
                    reqdiff += diff.decode('utf-8')
                except UnicodeDecodeError:
                    try:
                        reqdiff += diff.decode('iso-8859-1')
                    except UnicodeDecodeError:
                        pass

            except (AttributeError, urllib2.HTTPError), err:
                print err
                return None

            return reqdiff

        def _gen_server_diff(req):
            """
            Reserved getter: get req diff, if and only if the Recommanded
            getter failed
            """
            reqdiff = ''

            src_project = req.actions[0].src_project
            src_package = req.actions[0].src_package
            src_rev = req.actions[0].src_rev
            try:
                dst_project = req.actions[0].dst_project
                dst_package = req.actions[0].dst_package
            except AttributeError:
                dst_project = req.actions[0].tgt_project
                dst_package = req.actions[0].tgt_package

            if not self.exists(src_project, src_package):
                src_fl = self.get_src_file_list(src_project, src_package, \
                        src_rev)

                spec_file = None
                yaml_file = None
                for _file in src_fl:
                    if _file.endswith(".spec"):
                        spec_file = _file
                    elif _file.endswith(".yaml"):
                        yaml_file = _file

                reqdiff += 'This is a NEW package in %s project.\n' % \
                        dst_project

                reqdiff += 'The files in the new package:\n'
                reqdiff += '%s/\n' % src_package
                reqdiff += '  |__  ' + '\n  |__  '.join(src_fl)

                sep_line = '=' * 67

                if yaml_file:
                    reqdiff += '\n\nThe content of the YAML file, %s:\n' % \
                            yaml_file
                    reqdiff += '%s\n' % sep_line
                    reqdiff += self.get_src_file_content(src_project, \
                            src_package, yaml_file, src_rev)
                    reqdiff += '\n%s\n' % sep_line

                if spec_file:
                    reqdiff += '\n\nThe content of the spec file, %s:\n' % \
                            spec_file
                    reqdiff += '%s\n' % sep_line
                    reqdiff += self.get_src_file_content(src_project, \
                            src_package, spec_file, src_rev)
                    reqdiff += '\n%s\n' % sep_line
                else:
                    reqdiff += '\n\nspec file NOT FOUND!\n'

            else:
                try:
                    diff = core.server_diff(self.apiurl,
                                        dst_project, dst_package, None,
                                        src_project, src_package, src_rev, \
                                                False)

                    try:
                        reqdiff += diff.decode('utf-8')
                    except UnicodeDecodeError:
                        try:
                            reqdiff += diff.decode('iso-8859-1')
                        except UnicodeDecodeError:
                            pass

                except (urllib2.HTTPError, urllib2.URLError), err:
                    err.osc_msg = 'Error getting diff between %s/%s and '\
                                  '%s/%s rev.%s: %s' % \
                                  (dst_project, dst_package, src_project,
                                   src_package, src_rev, str(err))
                    return ''

            return reqdiff

        ####################################
        # function implementation start here

        req = core.get_request(self.apiurl, reqid)
        try:
            req.reviews = []
            reqinfo = unicode(req)
        except UnicodeEncodeError:
            reqinfo = u''

        if show_detail:
            diff = _gen_request_diff()
            if diff is None:
                diff = _gen_server_diff(req)

            reqinfo += diff

        # the result, in unicode string
        return reqinfo

    @retry()
    def exists(self, prj, pkg=''):
        """ Overwrite the exists function with retry decorator
        """
        try:
            return super(BuildService, self).exists(prj, pkg)
        except ObsError:
            return False

    def get_request_list(self, dst_prj, dst_pkg, user='', req_type='', \
            req_state=()):
        """Get already existing request list to the same obs project
        """
        req_type = req_type or 'submit'
        req_state = list(req_state) or ['new', 'review']

        if not user:
            user = conf.get_apiurl_usr(self.apiurl)
        return core.get_request_list(self.apiurl, dst_prj, dst_pkg, user, \
                req_type=req_type, req_state=req_state)

    def submit_req(self, src_prj, src_pkg, dst_prj, dst_pkg, msg='', orev=None,
            src_update='cleanup'):
        """Submit request to obs dest project
        """
        return core.create_submit_request(self.apiurl,
                                          src_prj, src_pkg,
                                          dst_prj, dst_pkg,
                                          msg, orev=orev, src_update=src_update)

    def req_decline(self, reqid, msg=''):
        """ This method is called to decline a request
        """
        core.change_request_state(self.apiurl, reqid, 'declined', \
                message=msg, supersed=None)

    def req_supersede(self, reqid, msg='', supersed=None):
        """ This method is called to supersede a request
        """
        core.change_request_state(self.apiurl, reqid, 'superseded', msg, \
                supersed)

    def get_repo_state(self, project):
        """Get repo's state of the project"""
        targets = {}
        try:
            tree = ElementTree.fromstring(''.join(core.show_prj_results_meta( \
                    self.apiurl, project)))
        except ElementTree.ParseError:
            raise ElementTree.ParseError( ''.join(core.show_prj_results_meta( \
                    self.apiurl, project)))

        for result in tree.findall('result'):
            targets[('/'.join((result.get('repository'), result.get('arch')))) \
                    ] = result.get('state')
        return targets

    def get_repo_state_summary(self, project):
        """Get repo's state summary of the project"""
        targets = {}
        query = []
        query.append('view=summary')

        u = core.makeurl(self.apiurl, ['build', project, '_result'],query=query)
        try:
            f = core.http_GET(u)
            tree = ElementTree.fromstring(''.join(f.readlines()))
        except urllib2.HTTPError, err:

            raise err

        for result in tree.findall('result'):
            targets[('/'.join((result.get('repository'), result.get('arch')))) \
                    ] = result.get('state')
        return targets

    def get_repo_config(self, project, repo):
        """
        get_repo_config(project) -> string

        Get buliding config of repo
        """
        return ''.join(core.get_buildconfig(self.apiurl, project, repo))

    def get_targets(self, project):
        """
        get_targets(project) -> list

        Get a list of targets for a project
        """
        targets = []
        tree = ElementTree.fromstring(''.join(core.show_project_meta( \
                self.apiurl, project)))
        for repo in tree.findall('repository'):
            for arch in repo.findall('arch'):
                targets.append('%s/%s' % (repo.get('name'), arch.text))
        return targets

    def get_package_list(self, prj, deleted=None):
        """Get package list of the project"""
        query = {}
        if deleted:
            query['deleted'] = 1
        else:
            query['deleted'] = 0

        url = core.makeurl(self.apiurl, ['source', prj], query)
        _file = core.http_GET(url)
        root = ElementTree.parse(_file).getroot()
        return [node.get('name') for node in root.findall('entry')]

    def get_package_build_result(self, project, status_filter=None):
        """ Get built result of the project """

        url = core.makeurl(self.apiurl, ['build', project, '_result'])
        _file = core.http_GET(url)

        build_result = {}
        root = ElementTree.parse(_file).getroot()
        for project_status in root.findall('result'):
            repo = project_status.attrib['repository']
            if repo not in build_result:
              build_result[repo] = {}
            arch = project_status.attrib['arch']
            if arch not in build_result[repo]:
              build_result[repo][arch] = {}
            for package_status in project_status.findall('status'):
                package_name = package_status.attrib['package']
                package_status = package_status.attrib['code']
                if status_filter == None or package_status in status_filter:
                    build_result[repo][arch][package_name] = package_status

        return build_result

    def get_build_log(self, project, target, package, offset=0):
        """
        get_build_log(project, target, package, offset=0) -> str

        Returns the build log of a package for a particular target.

        If offset is greater than 0, return only text after that offset.
        This allows live streaming
        """

        (repo, arch) = target.split('/')
        url = core.makeurl(self.apiurl, ['build', project, repo, arch, package,
                '_log?nostream=1&start=%s' % offset])
        return core.http_GET(url).read()

    def get_project_config(self, project):
        """
        get_project_config(project) -> string

        Get buliding config of project
        """
        return ''.join(core.show_project_conf(self.apiurl, project))

    def set_project_config(self, project, config):
        """
        Set building config of project
        """
        url = core.make_meta_url("prjconf", core.quote_plus(project),
                                 self.apiurl, False)
        self.core_http(core.http_PUT, url, data=''.join(config))

    def get_package_meta(self, project, package):
        """
        get_package_meta(project, package) -> string

        Get XML metadata for package in project
        """

        return ''.join(core.show_package_meta(self.apiurl, project, package))

    def get_package_real_project_name(self, project, package):
        """
        get_package_real_project_name(project, package) -> string

        Get real project name in metadata of this package
        """
        try:
            root = ElementTree.fromstring(self.get_package_meta(project, \
                    package))
            realprj = root.attrib['project']
        except (urllib2.HTTPError, KeyError), err:
            print err
            realprj = ''

        return realprj

    def delete_package(self, project, package):
        """
        delete_package(project, package)

        Delete the specific package in project
        """

        core.delete_package(self.apiurl, project, package)

    def undelete_package(self, project, package):
        """
        undelete_package(project, package)

        undelete the specific package in project
        """

        core.undelete_package(self.apiurl, project, package)

    def checkout(self, prj, pkg, rev='latest'):
        """ checkout the package to current dir with link expanded
        """

        core.checkout_package(self.apiurl, prj, pkg, rev, prj_dir=prj, \
                expand_link=True)

    def get_comments(self, project, package=None):
        """
        Get comments of the package
        """

        if package is not None:
            url = core.makeurl(self.apiurl, ['comments', 'package', project, package])
        else:
            url = core.makeurl(self.apiurl, ['comments', 'project', project])
        cmtroot = ElementTree.parse(core.http_GET(url)).getroot()
        tag_ids = {}
        for headTag in cmtroot.getchildren():
            tag_ids[headTag.attrib['id']] = headTag.text
        return tag_ids

    def set_comments(self, project, package=None, comment=''):
        """
        Get comments of the package
        """

        if package is not None:
            url = core.makeurl(self.apiurl, ['comments', 'package', project, package])
        else:
            url = core.makeurl(self.apiurl, ['comments', 'project', project])
        self.core_http(core.http_POST, url, data='%s' % comment)

    def purge_comments(self, project, package=None):
        """
        Delete the specific file from package in project
        """

        for t in self.get_comments(project, package):
            url = core.makeurl(self.apiurl, ['comment', t])
            self.core_http(core.http_DELETE, url)

    def delete_file(self, project, package, files=[], comment=None, purge_comments=False):
        """
        Delete the specific file from package in project
        """

        core.delete_files(self.apiurl, project, package, files)

        if purge_comments == True:
            self.purge_comments(project, package)

        if comment is not None:
            self.set_comments(project, package, comment)

    @staticmethod
    def find_pac(work_dir='.'):
        """Get the single Package object for specified dir
          the 'work_dir' should be a working dir for one single pac
        """

        if core.is_package_dir(work_dir):
            return core.findpacs([work_dir])[0]
        else:
            return None

    def mk_pac(self, prj, pkg):
        """Create empty package for new one under CWD
        """

        core.make_dir(self.apiurl, prj, pkg, pathname='.')

        pkg_path = os.path.join(prj, pkg)
        shutil.rmtree(pkg_path, ignore_errors=True)
        os.chdir(prj)
        core.createPackageDir(pkg)

    @staticmethod
    def submit(msg, work_dir='.'):
        """Submit to obs"""
        if not core.is_package_dir(work_dir):
            raise ObsError('%s not dir' % work_dir)
        pac = core.findpacs([work_dir])[0]
        prj = os.path.normpath(os.path.join(pac.dir, os.pardir))
        pac_path = os.path.basename(os.path.normpath(pac.absdir))
        files = {}
        files[pac_path] = pac.todo
        try:
            core.Project(prj).commit(tuple([pac_path]), msg=msg, files=files)
        except urllib2.HTTPError, err:
            raise ObsError('%s' % err)
        core.store_unlink_file(pac.absdir, '_commit_msg')

    def branch_pkg(self, src_project, src_package, rev=None, \
            target_project=None, target_package=None):
        """Create branch package from `src_project/src_package`
          arguments:
            rev: revision of src project/package
            target_project: name of target proj, use default one if None
            target_package: name of target pkg, use the same as asrc if None
        """

        if target_project is None:
            target_project = 'home:%s:branches:%s' \
                             % (conf.get_apiurl_usr(self.apiurl), src_project)

        if target_package is None:
            target_package = src_package

        targetprj, targetpkg = core.branch_pkg(self.apiurl, src_project,
                                               src_package, rev=rev,
                                               target_project=target_project,
                                               target_package=target_package,
                                               force=True)[1:3]

        return (targetprj, targetpkg)

    def update_buildstatus(self,info,prj,pkg=None):
        saved_info = self.get_info(prj, pkg)
        saved_info["buildstatus"]=info["buildstatus"]
        self.set_description(json.dumps(saved_info), prj, pkg)

    def update_info(self, info, prj, pkg=None):
        """Updated jsoned info dictionary, saved in description."""
        saved_info = self.get_info(prj, pkg)

        projects = saved_info.get('projects') or []
        packages = saved_info.get('packages') or []
        images = saved_info.get('images') or []
        submitter = saved_info.get('submitter') or ''
        download_num = saved_info.get('download_num') or int(0)
        submissions = saved_info.get('submissions') or []
        github = saved_info.get('github') or []
        saved_info.update(info)
        if 'projects' in info:
            saved_info['projects'] = list(set(projects + info['projects']))
        if 'packages' in info:
            saved_info['packages'] = list(set(packages + info['packages']))
        if 'submitter' in info:
            saved_info['submitter'] = ','.join(list(set(submitter.split(',') \
                                      + info['submitter'].split(','))))
        if 'images' in info:
            if info['images']:
                # remove the old one if already exist
                for image in images:
                    if info['images'][0]['name'] == image['name']:
                        if 'repo' in info['images'][0] and 'repo' in image:
                            if info['images'][0]['repo'] == image['repo']:
                                images.remove(image)
                        else:
                            images.remove(image)

                saved_info['images'] = images + info['images']
            else:
                # reset 'images'
                saved_info['images'] = info['images']
            if 'snapshot' in info:
                saved_info['snapshot'] = info['snapshot']
            if 'chksnap' in info:
                saved_info['chksnap'] = info['chksnap']
        if 'download_num' in info:
            saved_info['download_num'] = int(download_num) + int(info['download_num'])
        if 'submissions' in info:
            for s in info['submissions']:
                submissions = filter(lambda a: a != s, submissions)
            saved_info['submissions'] = submissions + info['submissions']
        if 'github' in info:
            for key, value in github.items():
                if key not in saved_info['github']:
                    saved_info['github'].update({key:value})

        # Retry three times if failed
        for retry_cnt in [1, 2, 3]:
            try:
                sleep(1)
                ret_inst = self.set_description(json.dumps(saved_info), prj, pkg)
                import urllib
                if ret_inst is None or isinstance(ret_inst, urllib.addinfourl) != True:
                    return
                if ElementTree.fromstring(ret_inst.read()).get('code') == 'ok':
                    return
            except Exception as err:
                print repr(err)

    def get_info(self, prj, pkg=None):
        """Get info dictionary, saved in description."""

        for count in (1, 2, 3):
            description = self.get_description(prj, pkg)
            if not description: continue
            try:
                return json.loads(description)
            except ValueError:
                continue
        return {}

    def cleanup(self, obs_project, msg):
        """Remove prerelease OBS project."""
        if self.exists(obs_project):
            return self.delete_project(obs_project, force=True, msg=msg)

    def get_arch(self, project, repo_name):
        """Get arch though project and repo name"""

        meta_xml = self.get_meta(project)
        xml_root = ElementTree.fromstringlist(meta_xml)
        for repo_setting in xml_root.findall('repository'):
            if repo_setting.attrib['name'] == repo_name:
                return [node.text for node in repo_setting.findall('arch')]
        return []

    def get_package_linkinfo(self, project, package):
        """Get the link info of package
           If exists 'link', return link project, link package.
           otherwise, return None, None
        """
        try:
            if '_link' in core.meta_get_filelist(self.apiurl,
                                                 project, package):
                url = core.makeurl(self.apiurl, ['source', project, package])
                _file = self.core_http(core.http_GET, url)
                root = ElementTree.parse(_file).getroot()
                info = root.find('linkinfo')
                link_project = info.get('project')
                link_package = info.get('package')
                return link_project, link_package
        except (urllib2.URLError, urllib2.HTTPError), err:
            print "can't get the link project and the link package, "\
                  "because %s" % err
            return None, None
        return None, None

    @staticmethod
    def create_link_pac(src_project, src_package, dst_project,
                        dst_package, force=True):
        """create a linked package
           - "src" is the original package
           - "dst" is the "link" package that we are creating here
           Forced overwrite of existing _link file, when force is True
        """
        core.link_pac(src_project, src_package, dst_project,
                       dst_package, force)

    def link_project(self, project, src=None, linktype="all"):
        """
        modify the meta conf to make it linked with src project
        """
        if src and not self.exists(src):
            raise ObsError('base project: %s not exists' % src)

        if not self.exists(project):
            raise ObsError('project: %s not exists' % project)

        meta_xml = self.get_meta(project)
        xml_root = ElementTree.fromstringlist(meta_xml)

        if xml_root.find('link') is not None:
            raise ObsError('project: %s already linked with %s'
                           %(project, xml_root.find('link').get('project')))
        # Add linked project
        link_element = ElementTree.Element('link',
                                           {"project": "%s" %src})
        xml_root.append(link_element)

        # Set linkedbuild attribute for all repositories
        if linktype is not None:
            for repo_element in xml_root.findall('repository'):
                repo_element.set('linkedbuild', linktype)

        self.set_meta(ElementTree.tostring(xml_root), project)

    def link_projectDev(self, project, src=None, linktype="all", buildtype="local", blocktype="local", repo=None):
        """
        modify the meta conf to make it linked with src project
        """
        if src and not self.exists(src):
            raise ObsError('base project: %s not exists' % src)

        if not self.exists(project):
            raise ObsError('project: %s not exists' % project)

        meta_xml = self.get_meta(project)
        xml_root = ElementTree.fromstringlist(meta_xml)

        if xml_root.find('link') is not None:
            raise ObsError('project: %s already linked with %s'
                           %(project, xml_root.find('link').get('project')))
        # Add linked project
        link_element = ElementTree.Element('link',
                                           {"project": "%s" %src})
        xml_root.append(link_element)

        # remove repo
        if not repo == None:
            if type(repo) is str:
                repo = [repo]
            for repo_element in xml_root.findall('repository'):
                if repo_element.get('name') not in repo:
                    xml_root.remove(repo_element)

        # Set linkedbuild attribute for all repositories
        for repo_element in xml_root.findall('repository'):
            repo_element.set('linkedbuild', linktype)
            repo_element.set('rebuild', buildtype)
            repo_element.set('block', blocktype) 

        self.set_meta(ElementTree.tostring(xml_root), project)

    def unlink_project(self, project):
        """
        modify the meta conf to unlink the project
        """
        if not self.exists(project):
            raise ObsError('project: %s not exists' % project)

        meta_xml = self.get_meta(project)
        xml_root = ElementTree.fromstringlist(meta_xml)

        if xml_root.find('link') is None:
            raise ObsError('project: %s has not links' %(project))

        # remov linked project
        xml_root.remove(xml_root.find('link'))
        # Remove linkedbuild attribute from all repositories
        for repo_element in xml_root.findall('repository'):
            if repo_element.attrib.get('linkedbuild') is not None:
                repo_element.attrib.pop('linkedbuild')

        self.set_meta(ElementTree.tostring(xml_root), project)

    def get_link_project_info(self, project):
        """
        Retrieve link project info
        """
        if not self.exists(project):
            return None

        meta_xml = self.get_meta(project)
        xml_root = ElementTree.fromstringlist(meta_xml)

        link_element = xml_root.find('link')
        if link_element is None:
            return None

        return link_element.attrib.get('project')

    def create_sr(self, src_project, packages, tgt_project, message=''):
        """Create submit request for the project."""
        content = '<request><description>%s</description>' % \
                  cgi.escape(str(message))
        for package in packages:
            content += '<action type="submit">'
            content += '<source project="%s" package="%s"/>' % \
                       (str(src_project), str(package))
            content += '<target project="%s" package="%s" />' % \
                       (str(tgt_project), str(package))
            content += '</action>'
        content += '</request>\n'
        url = core.makeurl(self.apiurl, ['request'], query='cmd=create')
        reply = self.core_http(core.http_POST, url, data=content)
        return ElementTree.parse(reply).getroot().get('id')

    def set_sr_state(self, reqid, state, message='', force=False):
        """Set SR state."""
        return core.change_request_state(self.apiurl, reqid, state,
                   message=str(message), force=force)

    def runservice(self, project, package):
        """Run remote service for the package."""
        return core.runservice(self.apiurl, project, package)

    def set_global_flag(self, flag, value, project, package=None):
        """
        Set global flag in meta
        Supported flag: publish,build,useforbuild,debuginfo
        Supported values: enable,disable
        """
        supported_flags = ('publish', 'build', 'useforbuild', 'debuginfo')
        if flag not in supported_flags:
            raise ObsError("flag %s is not supported. "
                                "supported flags: %s" % \
                                    (flag, ', '.join(supported_flags)))
        supported_vals = ('enable', 'disable')
        if value not in supported_vals:
            raise ObsError("value %s is not supported. "
                                "supported values: %s" % \
                                (value, ', '.join(supported_vals)))
        meta = self.get_meta(project, package)
        root = ElementTree.fromstring(meta)
        elem = root.find(flag)
        if elem is None:
            elem = ElementTree.SubElement(root, flag)
        else:
            # remove all globel subelements, i.e. subelements without
            # properties: <enable/> or <disable/>
            for subelem in list(elem):
                if not subelem.keys():
                    elem.remove(subelem)
        ElementTree.SubElement(elem, value)
        self.set_meta(ElementTree.tostring(root), project, package)

    def get_user_meta(self,apiurl, user):
        u = core.makeurl(apiurl, ['person', core.quote_plus(user)])
        try:
            f = core.http_GET(u)
            return ''.join(f.readlines())
        except urllib2.HTTPError:
            print 'user \'%s\' not found' % user
            return None

    def create_copy_pac(self, src_project, src_package, dst_project, dst_package,
                        client_side_copy=False, keep_maintainers=False,keep_develproject=False,
                        expand=False, revision=None, comment=None,
                        force_meta_update=None, keep_link=None):
        """create a copy of package
           Copying can be done by downloading the files from one package and commit
           them into the other by uploading them (client-side copy) --
           or by the server, in a single api call.
        """
        core.copy_pac(self.apiurl, src_project, src_package, self.apiurl, dst_project, dst_package,
                      client_side_copy,revision)

    def create_copy_pac_from_remote(self, src_project, src_package, dst_project, dst_package):
        """create a copy of package
           Copying can be done by downloading the files from one package and commit
           them into the other by uploading them (client-side copy) --
           or by the server, in a single api call.
        """
        if self.apiurl != self.remote_apiurl:
            client_side_copy = True
            expand = True
            keep_link = False
        else:
            client_side_copy = False
            expand = False
            keep_link = False

        rev = core.show_upstream_rev(self.remote_apiurl, src_project, src_package)
        comment = 'copypac from project:%s package:%s revision:%s' % \
                  ( src_project, src_package, rev )
        if keep_link:
            comment += ", using keep-link"
        if expand:
            comment += ", using expand"
        if client_side_copy:
            comment += ", using client side copy"

        core.copy_pac(self.remote_apiurl, src_project, src_package,
                      self.apiurl, dst_project, dst_package,
                      client_side_copy=client_side_copy, 
                      expand=expand,
                      comment=comment,
                      keep_link=keep_link)

    def get_dependson(self, project, repo, arch, packages=None, reverse=None):
        """
        get_dependson
        """
        query = []

        if packages:
            for p in packages:
                query.append('package=%s' % core.quote_plus(p))

        if reverse:
            query.append('view=revpkgnames')
        else:
            query.append('view=pkgnames')

        url = core.makeurl(self.apiurl, ['build', project, repo, arch,'_builddepinfo'],query=query)
        return core.http_GET(url).read()

    def show_upstream_rev(self, project, package, revision=None, expand=False, linkrev=None, meta=False, include_service_files=False):
        """
        show upstream rev
        """
        return core.show_upstream_rev(self.apiurl, project, package, revision, expand, linkrev, meta, include_service_files)

    def disable_build_flag(self, prj, repo, flag, status):
        """disable build flag for the project """
        #Started POST "/source/acl-link?cmd=set_flag&flag=build&status=disable"
        query = { 'cmd': 'set_flag' }
        if flag:
            query['flag'] = flag
        if repo:
            query['repository'] = repo
        if status:
            query['status'] = status

        u = core.makeurl(self.apiurl, ['source', prj], query=query)
        try:
            f = core.http_POST(u)
        except urllib2.HTTPError, e:
            e.osc_msg = 'could not trigger disable build flag for project \'%s\'' % (prj)
            raise

        return

    def default_build_flag(self, prj, repo, flag):
        """default build flag for the project """
        # Started POST "/source/acl-link?cmd=remove_flag&flag=build
        query = { 'cmd': 'remove_flag' }
        if repo:
            query['repository'] = repo
        if flag:
            query['flag'] = flag

        u = core.makeurl(self.apiurl, ['source', prj], query=query)
        try:
            f = core.http_POST(u)
        except urllib2.HTTPError, e:
            e.osc_msg = 'could not trigger default build flag for project \'%s\'' % (prj)
            raise
        return


    def search_status_package(self, prj, interest='failed', pkg=None, repo=None, arch=None, flag=True):
        #u = core.show_results_meta(self.apiurl, prj)
        u = core.show_prj_results_meta(self.apiurl, prj)
        try:
            tree = ElementTree.fromstring(''.join(u))
        except:
            raise
        packages = {}
        for result in tree.iter('result'):
            for status in result.iter('status'):
                if interest in status.attrib['code']:
                    pkgname = status.attrib['package']
                    list1 = []
                    list1.append({'repository':result.get('repository'),\
                                  'arch':result.get('arch')})
                    if packages.get(pkgname):
                        for x in packages.get(pkgname):
                            list1.append(x)
                    packages[pkgname] = list1
        return packages

    def get_build_results(self, prj, view=None, code=None,):
        """ get build results """

        query = []
        if view:
            query.append('view=%s' %(view))
        else:
            query.append('view=summary')

        if code:
            query.append('code=%s' %(code))

        u = core.makeurl(self.apiurl, ['build', prj, '_result'],query=query)
        try:
            f = core.http_GET(u)
            return ''.join(f.readlines())
        except urllib2.HTTPError, err:
            print err
            return err

    def getbuildstatus(self, project):
        """   get build status """
        resultdata = {}
        summarylist = []
        results_summary = self.get_build_results(project)
        et = ElementTree.fromstring(''.join(results_summary))
        for result in et.findall('result'):
            for summary in result.findall('summary'):
                for status in summary.findall('statuscount'):
                    if status.get('code') in ("succeeded","unresolvable","failed"):
                        summarylist.append({'repo':result.get('repository'), \
                                            'arch':result.get('arch'), \
                                            'code':status.get('code'), \
                                            'count':status.get('count')
                                            })
        resultdata['buildstatus'] = summarylist
        return resultdata

    def get_last_submitter(self, prj, pkg, req_state=('accepted', 'new')):
        u = core.get_request_list(self.apiurl, project=prj, package=pkg, req_state=req_state)
        submitter = ''
        reqid = ''
        for r in u:
            submitter = r.description.splitlines()[0].split(': ')[1].replace('&lt;','<').replace('&gt;','>')
            reqid = r.reqid
        return submitter

    def get_source_file(self, prj, pkg, filename, targetfilename=None):
        return core.get_source_file(self.apiurl, prj, pkg, filename, targetfilename=targetfilename)

    def get_source_info(self, prj, pkg):
        """ get source service info """

        u = core.makeurl(self.apiurl, ['source', prj, pkg])

        try:
            f = core.http_GET(u)
            return ''.join(f.readlines())
        except urllib2.HTTPError, err:
            print err
            return err

    def set_build_for_repo(self, prj=None, repos=None, flag=None, mode=None):
        """ control build flag """

        self.default_build_flag(prj, repo = None, flag='build')

        if repos == 'all':
            repos = self.get_repositories(prj)
        elif type(repos) is not list:
            repos = [repos]

        kind = 'prj'
        path = core.quote_plus(prj),
        data = core.meta_exists(metatype=kind,
                                path_args=path,
                                template_args=None,
                                create_new=False)
        if not data:
            return

        root = ElementTree.fromstring(''.join(data))

        rm_items = []
        for build in root.getiterator('build'):
            for item in build.getiterator():
                if item.tag not in ('disable', 'enable'):
                    continue
                if item.get('repository') in repos \
                    and item.get('arch') is None:
                    rm_items.append(item)
            for rm_item in rm_items:
                build.remove(rm_item)
            for repo in repos:
                build.insert(100, ElementTree.Element(mode, repository=repo)) #100 for append to tail

        core.edit_meta(metatype=kind,
                       path_args=path,
                       data=ElementTree.tostring(root))

    def addPerson(self, prj, users):
        """
        add persons to a project
        users = {'userid': ['maintainer', 'bugowner', ...], ...}
        """

        path = core.quote_plus(prj),
        kind = 'prj'
        data = core.meta_exists(metatype=kind,
                                path_args=path,
                                template_args=None,
                                create_new=False)
        if not data:
            return

        root = ElementTree.fromstring(''.join(data))
        for user in users:
            for role in users[user]:
                found = False
                for person in root.getiterator('person'):
                    if person.get('userid') == user and person.get('role') == role:
                        found = True
                        break
                if not found:
                    root.insert(2, ElementTree.Element('person', role=role, userid=user))
        core.edit_meta(metatype=kind,
                       path_args=path,
                       data=ElementTree.tostring(root))

    def get_published_repos(self, prj):
        """ 
        get published repos of the project
        """
        u = core.makeurl(self.apiurl, ['published', prj])
        f = core.http_GET(u)
        tree = ElementTree.parse(f)
        r = [ node.get('name') for node in tree.findall('entry')]
        return r

    def get_build_repos(self, prj):
        """
        get repos of the project
        """
        u = core.makeurl(self.apiurl, ['build', prj])
        f = core.http_GET(u)
        tree = ElementTree.parse(f)
        r = [ node.get('name') for node in tree.findall('entry')]
        return r

    def get_binarylist(self, project, repository, arch, package='', verbose=False):
        return core.get_binarylist(self.apiurl, project, repository, arch, package=package, verbose=verbose)

    def get_sourceinfo_list(self, prj):
        """
        Get source info list of the project
        { package1: [],
          package2: ['Source Project'/'Source Package', 'Source Project2'/'Source Package2' ], # if linked package
          ...}
        Note: If more than one linked packages found, use the last one.
        """
        query = {}
        query['view'] = 'info'
        query['parse'] = 0
        query['nofilename'] = 1

        url = core.makeurl(self.apiurl, ['source', prj], query)
        _file = core.http_GET(url)
        root = ElementTree.parse(_file).getroot()

        s_dict = {}
        for s in root.iter('sourceinfo'):
            s_dict[s.get('package')] = \
                [node.get('project') + '/' +  node.get('package') \
                 for node in s.findall('linked') \
                 if node.get('project') == prj ]
        return s_dict

    def is_lock_project(self, project):
        """
        is lock project
        """
        flag = 'lock'
        value = 'enable'
        meta = self.get_meta(project)
        root = ElementTree.fromstring(meta)
        elem = root.find(flag)
        if elem is None:
            return False

        return True

    def set_lock_project(self, project):
        """
        set lock project in meta
        """
        flag = 'lock'
        value = 'enable'
        meta = self.get_meta(project)
        root = ElementTree.fromstring(meta)
        elem = root.find(flag)
        if elem is None:
            elem = ElementTree.SubElement(root, flag)
        else:
            # remove all globel subelements, i.e. subelements without
            # properties: <enable/> or <disable/>
            for subelem in list(elem):
                if not subelem.keys():
                    elem.remove(subelem)
        ElementTree.SubElement(elem, value)
        self.set_meta(ElementTree.tostring(root), project)

    def set_unlock_project(self, project, comment):
        """
        set unlock project
        """
        flag = 'lock'
        value = 'enable'
        meta = self.get_meta(project)
        root = ElementTree.fromstring(meta)
        elem = root.find(flag)
        if elem is None:
            raise ObsError("This project is not locked status")

        query = { 'cmd': 'unlock' }
        if comment:
            query['comment'] = comment
        else:
            raise ObsError("could not unlock for project")

        u = core.makeurl(self.apiurl, ['source', project], query=query)
        try:
            f = core.http_POST(u)
        except urllib2.HTTPError, e:
            raise ObsError("could not unlock for project \'%s\'" % (project))

        return

    def get_project_list(self):
        """Get list of projects matching regexp."""
        try:
            projects = core.meta_get_project_list(self.apiurl)
        except Exception, err:
            raise ObsError("cat't get list of projects from %s: %s" %
                                (self.apiurl, err))
        return projects

    def get_repositories(self, project):
        targets = []
        tree = ElementTree.fromstring(''.join(core.show_project_meta( \
                self.apiurl, project)))
        for repo in tree.findall('repository'):
            targets.append('%s' % repo.get('name'))
        return targets

    def get_dependson_from_snapshot(self, url, project, repo, arch):
        """
        get revpkgdepends.xml from snapshot url
        """
        u = '%s/%s/%s_%s_%s_%s' % (url, 'builddata/depends', project, repo, arch, 'revpkgdepends.xml')

        try:
             f = core.http_GET(u)
             return ''.join(f.readlines())
        except urllib2.HTTPError:
             print 'get_dependson_from_snapshot http_GET(%s) error' % u
             return None

    def get_pkgrev_from_snapshot(self, url, project):
        """
        get pkgrevisions.xml  from snapshot url
        """
        u = '%s/%s/%s_%s' % (url, 'builddata/depends', project, 'pkgrevisions.xml')
        print u
        try:
            f = core.http_GET(u)
            return eval(''.join(f.readlines()))
        except urllib2.HTTPError:
            print 'get_pkgrev_from_snapshot http_GET(%s) error' % u
            return None

    def get_source_viewinfo(self, prj, pkg=None, parse=0, nofilename=1):
        """
        Get source viewinfo of the project
        """
        query = {}
        query['view'] = 'info'
        query['parse'] = parse
        query['nofilename'] = nofilename

        if pkg is not None:
            u = core.makeurl(self.apiurl, ['source', prj, pkg], query)
        else:
            u = core.makeurl(self.apiurl, ['source', prj], query)
        try:
            return core.http_GET(u)
        except (urllib2.URLError, urllib2.HTTPError), e:
            e.osc_msg = 'could not get viewinfo  for project \'%s\'' % (prj)
            raise
        return

    def rebuild(self, project, package, target=None, code=None):
        """
        rebuild(project, package, target, code=None)

        Rebuild 'package' in 'project' for 'target'. If 'code' is specified,
        all targets with that code will be rebuilt
        """
        try:
            if target:
                (repo, arch) = target.split('/')
            else:
                repo = None
                arch = None
            return core.rebuild(self.apiurl, project, package, repo, arch, code)
        except urllib2.HTTPError, err:
            raise ObsError('%s' % err)

    def restartbuild(self, project, package=None, target=None):
        """
        restart(project, package=None, target=None)

        Restart build of a package or all packages in a project
        """
        try:
            if target:
                (repo, arch) = target.split('/')
            else:
                repo = None
                arch = None
            return core.restartbuild(self.apiurl, project, package, arch, repo)
        except urllib2.HTTPError, err:
            raise ObsError('%s' % err)

    def wipebinaries(self, project, package=None, arch=None, repo=None, code=None):
        retry_count = 3
        while retry_count > 0:
            try:
                core.wipebinaries(self.apiurl, project, package, arch, repo, code)
                break
            except Exception, e:
                print 'Exception', e
                sleep(1)
                retry_count -= 1

    def get_cycle_packages(self, project, repo_arch):
        for repo in repo_arch:
            for idx, item in enumerate(repo_arch[repo]):
                cycle_packages = []
                dep_xml = self.get_dependson(project, repo, item.get('arch'))
                dep_tree = ElementTree.fromstring(dep_xml)
                for cycle in dep_tree.findall('cycle'):
                    for cycle_child in cycle.findall('package'):
                        cycle_packages.append(cycle_child.text)
                repo_arch[repo][idx]['cycles'].extend(list(set(cycle_packages)))
        return repo_arch

    def cycle_build_added_for_link_project(self, link_project, source_project):

        link_repo_arch = {}
        for x in self.get_targets(link_project):
            repo, arch = x.split('/')
            if repo not in link_repo_arch:
                link_repo_arch[repo] = []
            link_repo_arch[repo].append({'arch': arch, 'cycles': []})

        link_repo_arch = self.get_cycle_packages(link_project, link_repo_arch)

        for repo in link_repo_arch:
            for arch in link_repo_arch[repo]:
                if len(arch.get('cycles')) >= 1:
                    return link_repo_arch

        return None

    def get_path_project(self, project):
        """Get arch though project and repo name"""

        path_project = []
        meta_xml = self.get_meta(project)
        xml_root = ElementTree.fromstringlist(meta_xml)
        for repo_element in xml_root.findall('repository'):
            for element in repo_element.findall('path'):
                if element.get('project'):
                    path_project.append(element.get('project'))
        return list(set(path_project))

    def add_repositoris(self, prj, repo_arch_data):
        """
        add repository - architecture to a project
        repo_arch_data = {'repository': ['arch1', 'arch2', ...], ...}
        """

        path = core.quote_plus(prj),
        kind = 'prj'
        data = core.meta_exists(metatype=kind,
                                path_args=path,
                                template_args=None,
                                create_new=False)
        if not data:
            return

        root = ElementTree.fromstring(''.join(data))
        for repo in repo_arch_data:
            repo_elem = ElementTree.Element('repository', name=repo)
            for arch in repo_arch_data[repo]:
                arch_elem = ElementTree.Element('arch')
                arch_elem.text = arch
                repo_elem.insert(100, arch_elem)
            root.insert(100, repo_elem)

        core.edit_meta(metatype=kind,
                       path_args=path,
                       data=ElementTree.tostring(root))

    def add_path_project(self, prj, base_project):
        """
        add path project to each repository
        """

        base_repo_list = self.get_repositories(base_project)

        path = core.quote_plus(prj),
        kind = 'prj'
        data = core.meta_exists(metatype=kind,
                                path_args=path,
                                template_args=None,
                                create_new=False)
        if not data:
            return

        root = ElementTree.fromstring(''.join(data))

        for repo in root.findall('repository'):
            for r in base_repo_list:
                path_elem = ElementTree.Element('path', project=base_project, repository=r)
                repo.insert(100, path_elem)

        core.edit_meta(metatype=kind,
                       path_args=path,
                       data=ElementTree.tostring(root))

