# rhnRepository.py                         - Perform local repository functions.
#-------------------------------------------------------------------------------
# This module contains the functionality for providing local packages.
#
# Copyright (c) 2008--2014 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
#-------------------------------------------------------------------------------

## language imports
import os
import time
import glob
import cPickle
import sys
import types
from operator import truth
import xmlrpclib

## common imports
from spacewalk.common.rhnLib import parseRPMName
from spacewalk.common.rhnLog import log_debug, log_error
from spacewalk.common.rhnException import rhnFault
from spacewalk.common.rhnConfig import CFG
from spacewalk.common import rhnRepository
from spacewalk.common.rhnTranslate import _

## local imports
from rhn import rpclib


PKG_LIST_DIR = os.path.join(CFG.PKG_DIR, 'list')
PREFIX = "rhn"


class NotLocalError(Exception):
    pass


class Repository(rhnRepository.Repository):
    # pylint: disable=R0902
    """ Proxy local package repository lookup and manipulation code. """

    def __init__(self,
      channelName, channelVersion, clientInfo,
      rhnParent=None, rhnParentXMLRPC=None, httpProxy=None, httpProxyUsername=None,
      httpProxyPassword=None, caChain=None):

        log_debug(3, channelName)
        rhnRepository.Repository.__init__(self, channelName)
        self.functions = CFG.PROXY_LOCAL_FLIST
        self.channelName = channelName
        self.channelVersion = channelVersion
        self.clientInfo = clientInfo
        self.rhnParent = rhnParent
        self.rhnParentXMLRPC = rhnParentXMLRPC
        self.httpProxy = httpProxy
        self.httpProxyUsername = httpProxyUsername
        self.httpProxyPassword = httpProxyPassword
        self.caChain = caChain

    def getPackagePath(self, pkgFilename, redirect=0):
        """ OVERLOADS getPackagePath in common/rhnRepository.
            Returns complete path to an RPM file.
        """

        log_debug(3, pkgFilename)
        mappingName = "package_mapping:%s:" % self.channelName
        pickledMapping = self._cacheObj(mappingName, self.channelVersion,
                                        self.__channelPackageMapping, ())

        mapping = cPickle.loads(pickledMapping)

        # If the file name has parameters, it's a different kind of package.
        # Determine the architecture requested so we can construct an
        # appropriate filename.
        if type(pkgFilename) == types.ListType:
            arch = pkgFilename[3]
            # Not certain if anything is needed here for Debian, but since what I've tested
            # works.   Leave it alone.
            if isSolarisArch(arch):
                pkgFilename = "%s-%s-%s.%s.pkg" % \
                    (pkgFilename[0],
                     pkgFilename[1],
                     pkgFilename[2],
                     pkgFilename[3])

        if not mapping.has_key(pkgFilename):
            log_error("Package not in mapping: %s" % pkgFilename)
            raise rhnFault(17, _("Invalid RPM package requested: %s")
                                 % pkgFilename)
        # A list of possible file paths. Always a list, channel mappings are
        # cleared on package upgrade so we don't have to worry about the old
        # behavior of returning a string
        filePaths = mapping[pkgFilename]
        # Can we see a file at any of the possible filepaths?
        for filePath in filePaths:
            filePath = "%s/%s" % (CFG.PKG_DIR, filePath)
            log_debug(4, "File path", filePath)
            if os.access(filePath, os.R_OK):
                return filePath
        log_debug(4, "Package not found locally: %s" % pkgFilename)
        raise NotLocalError(filePaths[0], pkgFilename)


    def getSourcePackagePath(self, pkgFilename):
        """ OVERLOADS getSourcePackagePath in common/rhnRepository.
            snag src.rpm and nosrc.rpm from local repo, after ensuring
            we are authorized to fetch it.
        """

        log_debug(3, pkgFilename)
        if pkgFilename[-8:] != '.s
rc.rpm' and pkgFilename[-10:] != '.nosrc.rpm':
            raise rhnFault(17, _("Invalid SRPM package requested: %s")
                                 % pkgFilename)

        # Connect to the server to get an authorizatio
n for downloading this
        # package
        server = rpclib.Server(self.rhnParentXMLRPC, proxy=self.httpProxy,
                               username=self.httpProxyUsername,
                               password=self.httpProxyPassword)
        if self.caChain:
            server.add_trusted_cert(self.caChain)

        try:
            retval = server.proxy.package_source_in_channel(
                        pkgFilename, self.channelName, self.clientInfo)
        except xmlrpclib.Fault, e:
            raise rhnFault(1000,
                    _("Error retrieving source package: %s") % str(e)), None, sys.exc_info()[2]
        if not retval:
            raise rhnFault(17, _("Invalid SRPM package requested: %s")
                                 % pkgFilename)

        if pkgFilename[-8:] != '.src.rpm':
            # We already know the filename ends in .src.rpm
            nvrea = list(parseRPMName(pkgFilename[:-8]))
            nvrea.append("src")
        else:
            # We already know the filename ends in .nosrc.rpm
            # otherwise we did not pass first if in this func
            nvrea = list(parseRPMName(pkgFilename[:-10]))
            nvrea.append("nosrc")

        filePaths = computePackagePaths(nvrea, source=1, prepend=PREFIX)
        for filePath in filePaths:
            filePath = "%s/%s" % (CFG.PKG_DIR, filePath)
            log_debug(4, "File path", filePath)
            if os.access(filePath, os.R_OK):
                return filePath
        log_debug(4, "Source package not found locally: %s" % pkgFilename)
        raise NotLocalError(filePaths[0], pkgFilename)

    def _cacheObj(self, fileName, version, dataProducer, params=None):
        """ The real workhorse for all flavors of listall
            It tries to pull data out of a file; if it doesn't work,
            it calls the data producer with the specified params to generate
            the data, which is also cached.

            Returns a string from a cache file or, if the cache file is not
            there, calls dataProducer to generate the object and caches the
            results
        """

        log_debug(4, fileName, version, params)
        fileDir = self._getPkgListDir()
        filePath = "%s/%s-%s" % (fileDir, fileName, version)
        if os.access(filePath, os.R_OK):
            # Slurp the file
            f = open(filePath, "r")
            data = f.read()
            f.close()
            return data

        # The file's not there; query the DB or whatever dataproducer used.
        if params is None:
            params = ()
        stringObject = dataProducer(*params)
        # Cache the thing
        cache(stringObject, fileDir, fileName, version)
        # Return the string
        return stringObject

    @staticmethod
    def _getPkgListDir():
        """ Creates and returns the directory for cached lists of packages.
            Used by _cacheObj.

            XXX: Problem exists here. If PKG_LIST_DIR can't be created
            due to ownership... this is bad... need to fix.
        """

        log_debug(3, PKG_LIST_DIR)
        if not os.access(PKG_LIST_DIR, os.R_OK | os.X_OK):
            os.makedirs(PKG_LIST_DIR)
        return PKG_LIST_DIR

    def _listPackages(self):
        """ Generates a list of objects by