code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# 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; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""dependency analyzer"""

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import inary.data.relation as relation
from inary.db.packagedb import PackageDB
import inary.sxml.autoxml as autoxml

class Dependency(relation.Relation, metaclass=autoxml.autoxml):
    a_type = [autoxml.String, autoxml.optional]

    def __str__(self):
        s = self.package
        if self.versionFrom:
            s += _(" version >= ") + self.versionFrom
        if self.versionTo:
            s += _(" version <= ") + self.versionTo
        if self.version:
            s += _(" version ") + self.version
        if self.releaseFrom:
            s += _(" release >= ") + self.releaseFrom
        if self.releaseTo:
            s += _(" release <= ") + self.releaseTo
        if self.release:
            s += _(" release ") + self.release
        if self.type:
            s += " (" + self.type + ")"
        return s

    def name(self):
        return self.package

    def satisfied_by_dict_repo(self, dict_repo):

        if self.package not in dict_repo:
            return False
        else:
            pkg = dict_repo[self.package]
            return self.satisfies_relation(pkg.version, pkg.release)

    def satisfied_by_installed(self):
        return relation.installed_package_satisfies(self)

    def satisfied_by_repo(self, packagedb=None):
        if not packagedb:
            packagedb = PackageDB()

        if not packagedb.has_package(self.package):
            return False
        else:
            pkg = packagedb.get_package(self.package)
            return self.satisfies_relation(pkg.version, pkg.release)

    # Added for AnyDependency, single Dependency always returns False
    def satisfied_by_any_installed_other_than(self, package):
        pass


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# 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; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.

from . import fetcher

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import inary

import inary.atomicoperations
import inary.blacklist
import inary.config
import inary.context as ctx

# DataFile Libraries
import inary.data
import inary.data.index
import inary.data.metadata
import inary.data.pgraph

# DataBase Libraries
import inary.db.componentdb
import inary.db.packagedb
import inary.db.repodb
import inary.db.filesdb
import inary.db.installdb
import inary.db.historydb
import inary.db.sourcedb
import inary.db.componentdb
import inary.db.groupdb

import inary.errors
import inary.file

# Operation Libraries
import inary.operations.build
import inary.operations.check
import inary.operations.emerge
import inary.operations.info
import inary.operations.install
import inary.operations.history
import inary.operations.helper
import inary.operations.remove
import inary.operations.repository
import inary.operations.search
import inary.operations.upgrade

import inary.uri
import inary.util

def set_scom(enable):
    """
    Set scom usage
    False means no preremove and postinstall scripts will be run
    @param enable: Flag indicating scom usage
    """
    ctx.scom = enable

def set_scom_updated(updated):
    """
    Set scom package update status
    @param updated: True if SCOM package is updated, else False
    """
    ctx.scom_updated = updated

def set_userinterface(ui):
    """
    Set the user interface where the status information will be send
    @param ui: User interface
    """
    ctx.ui = ui

def set_io_streams(stdout=None, stderr=None):
    """
    Set standart i/o streams
    Used by Buildfarm
    @param stdout: Standart output
    @param stderr: Standart input
    """
    if stdout:
        ctx.stdout = stdout
    if stderr:
        ctx.stderr = stderr

def set_dbus_sockname(sockname):
    """
    Set dbus socket file
    Used by YALI
    @param sockname: Path to dbus socket file
    """
    ctx.dbus_sockname = sockname

def set_dbus_timeout(timeout):
    """
    Set dbus timeout
    Used by YALI
    @param timeout: Timeout in seconds
    """
    ctx.dbus_timeout = timeout

def set_signal_handling(enable):
    """
    Enable signal handling. Signal handling in inary mostly used for disabling keyboard interrupts
    in critical paths.
    Used by YALI
    @param enable: Flag indicating signal handling usage
    """
    if enable:
        ctx.sig = inary.signalhandler.SignalHandler()
    else:
        ctx.sig = None

def set_options(options):
    """
    Set various options of inary
    @param options: option set

       >>> options = inary.config.Options()

           options.destdir     # inary destination directory where operations will take effect
           options.username    # username that for reaching remote repository
           options.password    # password that for reaching remote repository
           options.debug       # flag controlling debug output
           options.verbose     # flag controlling verbosity of the output messages
           options.output_dir  # build and delta operations package output directory
    """
    ctx.config.set_options(options)

# The following are INARY operations which constitute the INARY API
# Within functions
from inary.analyzer.conflict import calculate_conflicts
from inary.db.filesdb import rebuild_db
from inary.data.index import index
from inary.data.pgraph import package_graph
from inary.fetcher import fetch
from inary.operations.build import build, build_until
from inary.operations.check import check
from inary.operations.emerge import emerge
from inary.operations.helper import calculate_download_sizes, calculate_free_space_needed, get_package_requirements
from inary.operations.history import takeback, get_takeback_plan, snapshot
from inary.operations.info import info
from inary.operations.install import install, get_install_order
from inary.operations.remove import remove, get_remove_order
from inary.operations.repository import *
from inary.operations.upgrade import upgrade, get_upgrade_order, get_base_upgrade_order
from inary.operations.search import *

repo_name: SulinOS/inary

license: mit


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# 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; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""Archive module provides access to regular archive file types."""

# standard library modules
import os
import lzma
import stat
import errno
import shutil
import tarfile
import zipfile

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

# INARY modules

import inary.errors
import inary.util as util
import inary.context as ctx
import inary.uri
import inary.fetcher

class SourceArchiveError(inary.errors.Error):
    pass

class UnknownArchiveType(Exception):
    pass

class ArchiveHandlerNotInstalled(Exception):
    pass

# Proxy class inspired from tarfile._BZ2Proxy

class _LZMAProxy(object):
    """Small proxy class that enables external file object
       support for "r:bz2" and "w:bz2" modes. This is actually
       a workaround for a limitation in bz2 module's BZ2File
       class which (unlike gzip.GzipFile) has no support for
       a file object argument.
    """

    blocksize = 16 * 1024

    def __init__(self, fileobj, mode):
        self.fileobj = fileobj
        self.mode = mode
        self.name = getattr(self.fileobj, "name", None)
        self.init()

    def init(self):
        self.pos = 0
        if self.mode == "r":
            self.lzmaobj = lzma.LZMADecompressor()
            # self.fileobj.seek(0)
            self.buf = b""
        else:
            self.lzmaobj = lzma.LZMACompressor()

    def read(self, size):
        b = [self.buf]
        x = len(self.buf)
        while x < size:
            raw = self.fileobj.read(self.blocksize)
            if not raw:
                break
            try:
                data = self.lzmaobj.decompress(raw)
            except EOFError:
                break
            b.append(data)
            x += len(data)
        self.buf = b"".join(b)

        buf = self.buf[:size]
        self.buf = self.buf[size:]
        self.pos += len(buf)
        return buf

    def seek(self, pos):
        if pos < self.pos:
            self.init()
        self.read(pos - self.pos)

    def tell(self):
        return self.pos

    def write(self, data):
        self.pos += len(data)
        raw = self.lzmaobj.compress(data)
        self.fileobj.write(raw)

    def close(self):
        if self.mode == "w":
            raw = self.lzmaobj.flush()
            self.fileobj.write(raw)

class TarFile(tarfile.TarFile):

    @classmethod
    def lzmaopen(cls,
                 name=None,
                 mode="r",
                 fileobj=None,
                 compresslevel=None,
                 **kwargs):
        """Open lzma/xz compressed tar archive name for reading or writing.
           Appending is not allowed.
        """

        try:
            import lzma
        except ImportError:
            try:
                from backports import lzma
            except ImportError:
                raise tarfile.CompressionError("Lzma module is not available")

        if not compresslevel:
            compresslevel = ctx.config.values.build.compressionlevel

        if len(mode) > 1 or mode not in "rw":
            raise ValueError("mode must be 'r' or 'w'.")

        if 'w' in mode:
            if fileobj is not None:
                fileobj = _LZMAProxy(fileobj, mode)
            else:
                fileobj = lzma.LZMAFile(name, mode, preset=compresslevel)

        else:
            if fileobj is not None:
                fileobj = _LZMAProxy(fileobj, mode)
            else:
                fileobj = lzma.LZMAFile(name, mode)

        try:
            t = cls.taropen(name, mode, fileobj, **kwargs)
        except IOError:
            raise tarfile.ReadError(_(" \"{}\" is not a lzma file.").format(name))
        t._extfileobj = False
        return t

class ArchiveBase(object):
    """Base class for Archive classes."""

    def __init__(self, file_path, atype):
        self.file_path = file_path
        self.type = atype

    def unpack(self, target_dir, clean_dir=False):
        # first we check if we need to clean-up our working env.
        if os.path.exists(target_dir):
            if clean_dir:
                util.clean_dir(target_dir)

        if not os.path.exists(target_dir):
            os.makedirs(target_dir)

class ArchiveBinary(ArchiveBase):
    """ArchiveBinary handles binary archive files (usually distributed as
    .bin files)"""

    def __init__(self, file_path, arch_type="binary"):
        super(ArchiveBinary, self).__init__(file_path, arch_type)

    def unpack(self, target_dir, clean_dir=False):
        super(ArchiveBinary, self).unpack(target_dir, clean_dir)

        # we can't unpack .bin files. we'll just move them to target
        # directory and leave the dirty job to actions.py ;)
        target_file = os.path.join(target_dir,
                                   os.path.basename(self.file_path))
        shutil.copyfile(self.file_path, target_file)

class ArchiveBzip2(ArchiveBase):
    """ArchiveBzip2 handles Bzip2 archive files"""

    def __init__(self, file_path, arch_type="bz2"):
        super(ArchiveBzip2, self).__init__(file_path, arch_type)

    def unpack(self, target_dir, clean_dir=False):
        super(ArchiveBzip2, self).unpack(target_dir, clean_dir)
        self.unpack_dir(target_dir)

    def unpack_dir(self, target_dir):
        """Unpack Bzip2 archive to a given target directory(target_dir)."""

        output_path = util.join_path(target_dir,
                                     os.path.basename(self.file_path))
        if output_path.endswith(".bz2"):
            output_path = output_path[:-4]

        import bz2
        bz2_file = bz2.BZ2File(self.file_path)
        output = open(output_path, "w")
        output.write(bz2_file.read().decode("utf-8"))
        output.close()
        bz2_file.close()

class ArchiveGzip(ArchiveBase):
    """ArchiveGzip handles Gzip archive files"""

    def __init__(self, file_path, arch_type="gz"):
        super(ArchiveGzip, self).__init__(file_path, arch_type)

    def unpack(self, target_dir, clean_dir=False):
        super(ArchiveGzip, self).unpack(target_dir, clean_dir)
        self.unpack_dir(target_dir)

    def unpack_dir(self, target_dir):
        """Unpack Gzip archive to a given target directory(target_dir)."""

        output_path = util.join_path(target_dir,
                                     os.path.basename(self.file_path))
        if output_path.endswith(".gz"):
            output_path = output_path[:-3]

        import gzip
        gzip_file = gzip.GzipFile(self.file_path, "r")
        output = open(output_path, "w")
        output.write(gzip_file.read().decode("utf-8"))
        output.close()
        gzip_file.close()

class ArchiveLzma(ArchiveBase):
    """ArchiveLzma handles LZMA archive files"""

    def __init__(self, file_path, arch_type="lzma"):
        super(ArchiveLzma, self).__init__(file_path, arch_type)

    def unpack(self, target_dir, clean_dir=False):
        super(ArchiveLzma, self).unpack(target_dir, clean_dir)
        self.unpack_dir(target_dir)

    def unpack_dir(self, target_dir):
        """Unpack LZMA archive to a given target directory(target_dir)."""

        output_path = util.join_path(target_dir,
                                     os.path.basename(self.file_path))
        ext = ".lzma" if self.type == "lzma" else ".xz"
        if output_path.endswith(ext):
            output_path = output_path[:-len(ext)]

        lzma_file = lzma.LZMAFile(self.file_path)
        output = open(output_path, "w")
        output.write(lzma_file.read().decode("utf-8"))
        output.close()
        lzma_file.close()

class ArchiveTar(ArchiveBase):
    """ArchiveTar handles tar archives depending on the compression
    type. Provides access to tar, tar.gz and tar.bz2 files.

    This class provides the unpack magic for tar archives."""

    def __init__(self, file_path=None, arch_type="tar",
                 no_same_permissions=True,
                 no_same_owner=False,
                 fileobj=None):
        super(ArchiveTar, self).__init__(file_path, arch_type)
        self.tar = None
        self.no_same_permissions = no_same_permissions
        self.no_same_owner = no_same_owner
        self.fileobj = fileobj

    def unpack(self, target_dir, clean_dir=False):
        """Unpack tar archive to a given target directory(target_dir)."""
        super(ArchiveTar, self).unpack(target_dir, clean_dir)
        self.unpack_dir(target_dir)

    def maybe_nuke_pip(self, info):
        if not info.name.endswith(".egg-info"):
            return
        if not "site-packages" in info.name:
            return
        if not "/python" in info.name:
            return
        if not info.isreg():
            return
        if not os.path.isdir(info.name):
            return
        print("Overwriting stale pip install: /{}".format(info.name))
        shutil.rmtree(info.name)

    def unpack_dir(self, target_dir, callback=None):
        rmode = ""
        self.tar = None
        if self.type == 'tar':
            rmode = 'r:'
        elif self.type == 'targz':
            rmode = 'r:gz'
        elif self.type == 'tarbz2':
            rmode = 'r:bz2'
        elif self.type in ('tarlzma', 'tarxz'):
            self.tar = TarFile.lzmaopen(self.file_path, fileobj=self.fileobj)
        else:
            raise UnknownArchiveType

        if self.tar is None:
            self.tar = tarfile.open(self.file_path, rmode,
                                    fileobj=self.fileobj)

        oldwd = None
        try:
            # Don't fail if CWD doesn't exist (#6748)
            oldwd = os.getcwd()
        except OSError:
            pass

        ctx.ui.info(_("Target DIR: \"{}\"").format(target_dir), verbose=True)
        os.chdir(target_dir)

        for tarinfo in self.tar:
            if callback:
                callback(tarinfo, extracted=False)

            startservices = []
            if tarinfo.issym() and \
                    os.path.isdir(tarinfo.name) and \
                    not os.path.islink(tarinfo.name):
                # Changing a directory with a symlink. tarfile module
                # cannot handle this case.

                if os.path.isdir(tarinfo.linkname):
                    # Symlink target is a directory. Move old directory's
                    # content to this directory.
                    for filename in os.listdir(tarinfo.name):
                        old_path = util.join_path(tarinfo.name, filename)
                        new_path = util.join_path(tarinfo.linkname, filename)

                        if os.path.lexists(new_path):
                            if not os.path.isdir(new_path):
                                # A file with the same name exists in the
                                # target. Remove the one in the old directory.
                                os.remove(old_path)
                            continue

                        # try as up to this time
                        try:
                            os.renames(old_path, new_path)
                        except OSError as e:
                            # something gone wrong? [Errno 18] Invalid cross-device link?
                            # try in other way
                            if e.errno == errno.EXDEV:
                                if tarinfo.linkname.startswith(".."):
                                    new_path = util.join_path(
                                        os.path.normpath(os.path.join(os.path.dirname(tarinfo.name), tarinfo.linkname)),
                                        filename)
                                if not old_path.startswith("/"):
                                    old_path = "/" + old_path
                                if not new_path.startswith("/"):
                                    new_path = "/" + new_path
                                print("Moving:", old_path, " -> ", new_path)
                                os.system("mv -f {0} {1}".format(old_path, new_path))
                            else:
                                raise
                    try:
                        os.rmdir(tarinfo.name)
                    except OSError as e:
                        # hmmm, not empty dir? try rename it adding .old extension.
                        if e.errno == errno.ENOTEMPTY:
                            # if directory with dbus/pid file was moved we have to restart dbus
                            for (path, dirs, files) in os.walk(tarinfo.name):
                                if path.endswith("dbus") and "pid" in files:
                                    startservices.append("dbus")
                                    for service in ("NetworkManager", "connman", "wicd"):
                                        if os.path.isfile("/etc/scom/services/enabled/{}".format(service)):
                                            startservices.append(service)
                                            os.system("service {} stop".format(service))
                                    os.system("service dbus stop")
                                    break
                            os.system("mv -f {0} {0}.old".format(tarinfo.name))
                        else:
                            raise

                elif not os.path.lexists(tarinfo.linkname):
                    # Symlink target does not exist. Assume the old
                    # directory is moved to another place in package.
                    os.renames(tarinfo.name, tarinfo.linkname)

                else:
                    # This should not happen. Probably a packaging error.
                    # Try to rename directory
                    try:
                        os.rename(tarinfo.name,
                                  "{}.renamed-by-inary".format(tarinfo.name))
                    except:
                        # If fails, try to remove it
                        shutil.rmtree(tarinfo.name)

            try:
                self.tar.extract(tarinfo)
                for service in startservices: os.system("service {} start".format(service))
            except OSError as e:
                # Handle the case where an upper directory cannot
                # be created because of a conflict with an existing
                # regular file or symlink. In this case, remove
                # the old file and retry extracting.

                if e.errno != errno.EEXIST:
                    raise

                # For the path "a/b/c", upper_dirs will be ["a", "a/b"].
                upper_dirs = []
                head, tail = os.path.split(tarinfo.name)

                while head and tail:
                    upper_dirs.insert(0, head)
                    head, tail = os.path.split(head)

                for path in upper_dirs:
                    if not os.path.lexists(path):
                        break

                    if not os.path.isdir(path):
                        # A file with the same name exists.
                        # Remove the existing file.
                        os.remove(path)
                        break
                else:
                    # No conflicts detected! This is probably not the case
                    # mentioned here. Raise the same exception.
                    raise

                # Try to extract again.
                self.tar.extract(tarinfo)

                # Handle the case where new path is file, but old path is directory
                # due to not possible touch file c in /a/b if directory /a/b/c exists.
                if not e.errno == errno.EISDIR:
                    path = tarinfo.name
                    found = False
                    while path:
                        if os.path.isfile(path):
                            os.unlink(path)
                            found = True
                            break
                        else:
                            path = "/".join(path.split("/")[:-1])
                    if not found: raise
                    # Try to extract again.
                    self.tar.extract(tarinfo)
                else:
                    shutil.rmtree(tarinfo.name)
                    # Try to extract again.
                    self.tar.extract(tarinfo)

            # tarfile.extract does not honor umask. It must be honored
            # explicitly. See --no-same-permissions option of tar(1),
            # which is the deafult behaviour.
            #
            # Note: This is no good while installing a inary package.
            # Thats why this is optional.
            if self.no_same_permissions and not os.path.islink(tarinfo.name):
                os.chmod(tarinfo.name, tarinfo.mode & ~ctx.const.umask)

            if self.no_same_owner:
                uid = os.getuid()
                gid = os.getgid()

                if not os.path.islink(tarinfo.name):
                    ctx.ui.info(_("Chowning {0} ({1}:{2})").format(tarinfo.name, uid, gid), verbose=True)
                    os.chown(tarinfo.name, uid, gid)
                else:
                    ctx.ui.info(_("LChowning {0} ({1}:{2})").format(tarinfo.name, uid, gid), verbose=True)
                    os.lchown(tarinfo.name, uid, gid)

            if callback:
                callback(tarinfo, extracted=True)

        try:
            if oldwd:
                os.chdir(oldwd)

        # Bug #6748
        except OSError:
            pass

        self.close()

    def add_to_archive(self, file_name, arc_name=None):
        """Add file or directory path to the tar archive"""
        if not self.tar:
            if self.type == 'tar':
                wmode = 'w:'
            elif self.type == 'targz':
                wmode = 'w:gz'
            elif self.type == 'tarbz2':
                wmode = 'w:bz2'
            elif self.type in ('tarlzma', 'tarxz'):
                compresslevel = int(ctx.config.values.build.compressionlevel)
                self.tar = TarFile.lzmaopen(self.file_path, "w",
                                            fileobj=self.fileobj,
                                            compresslevel=compresslevel
                                            )
            else:
                raise UnknownArchiveType

            if self.tar is None:
                self.tar = tarfile.open(self.file_path, wmode,
                                        fileobj=self.fileobj)

        self.tar.add(file_name, arc_name)

    def close(self):
        self.tar.close()

class ArchiveTarZ(ArchiveBase):
    """ArchiveTar handles tar.Z archives.

    This class provides the unpack magic for tar.Z archives."""

    def __init__(self, file_path, arch_type="tarZ",
                 no_same_permissions=True, no_same_owner=True):
        super(ArchiveTarZ, self).__init__(file_path, arch_type)
        self.tar = None
        self.no_same_permissions = no_same_permissions
        self.no_same_owner = no_same_owner

    def unpack(self, target_dir, clean_dir=False):
        """Unpack tar archive to a given target directory(target_dir)."""
        super(ArchiveTarZ, self).unpack(target_dir, clean_dir)
        self.unpack_dir(target_dir)

    def unpack_dir(self, target_dir):
        self.file_path = util.remove_suffix(".Z", self.file_path)

        ret, out, err = util.run_batch(
            "uncompress -cf {0}.Z > {0}".format(self.file_path))
        if ret != 0:
            raise RuntimeError(_("Problem occured while uncompressing \"{}.Z\" file").format(self.file_path))

        self.tar = tarfile.open(self.file_path)

        oldwd = None
        try:
            # Don't fail if CWD doesn't exist (#6748)
            oldwd = os.getcwd()
        except OSError:
            pass
        os.chdir(target_dir)

        uid = os.getuid()
        gid = os.getgid()

        for tarinfo in self.tar:
            self.tar.extract(tarinfo)

            # tarfile.extract does not honor umask. It must be honored
            # explicitly. See --no-same-permissions option of tar(1),
            # which is the deafult behaviour.
            #
            # Note: This is no good while installing a inary package.
            # Thats why this is optional.
            if self.no_same_permissions and not os.path.islink(tarinfo.name):
                os.chmod(tarinfo.name, tarinfo.mode & ~ctx.const.umask)

            if self.no_same_owner:
                if not os.path.islink(tarinfo.name):
                    ctx.ui.info(_("Chowning {0} ({1}:{2})").format(tarinfo.name, uid, gid), verbose=True)
                    os.chown(tarinfo.name, uid, gid)
                else:
                    ctx.ui.info(_("LChowning {0} ({1}:{2})").format(tarinfo.name, uid, gid), verbose=True)
                    os.lchown(tarinfo.name, uid, gid)

        # Bug #10680 and addition for tarZ files
        os.unlink(self.file_path)

        try:
            if oldwd:
                os.chdir(oldwd)
        # Bug #6748
        except OSError:
            pass
        self.tar.close()

class Archive7Zip(ArchiveBase):
    """Archive7Zip handles 7-Zip archives."""

    def __init__(self, file_path, arch_type="7z"):
        super(Archive7Zip, self).__init__(file_path, arch_type)
        self.cmd = util.search_executable(arch_type)
        if not self.cmd:
            raise ArchiveHandlerNotInstalled

    def unpack(self, target_dir, clean_dir=False):
        super(Archive7Zip, self).unpack(target_dir, clean_dir)
        self.unpack_dir(target_dir)

    def unpack_dir(self, target_dir):
        """Unpack 7z archive to a given target directory(target_dir)."""

        # e.g. 7z x -bd -o<target_directory> <archive.7z>
        util.run_batch("{0} x -bd -o{1} {2}".format(self.cmd, target_dir, self.file_path))

class ArchiveZip(ArchiveBase):
    """ArchiveZip handles zip archives.

    Being a zip archive INARY packages also use this class
    extensively. This class provides unpacking and packing magic for
    zip archives."""

    symmagic = 2716663808  # long of hex val '0xA1ED0000L'

    def __init__(self, file_path, arch_type="zip", mode='r'):
        super(ArchiveZip, self).__init__(file_path, arch_type)
        try:
            self.zip_obj = zipfile.ZipFile(self.file_path, mode)
        except zipfile.BadZipFile:
            raise UnknownArchiveType(_("File \"{}\" is not a zip file.").format(self.file_path))

    def open(self, file_path, mode="r"):
        return self.zip_obj.open(file_path, mode)

    def close(self):
        """Close the zip archive."""
        self.zip_obj.close()

    def add_to_archive(self, file_name, arc_name=None):
        """Add file or directory path to the zip file"""
        # It's a pity that zipfile can't handle unicode strings. Grrr!
        file_name = str(file_name)
        if os.path.isdir(file_name) and not os.path.islink(file_name):
            arc_name = arc_name or ""
            self.zip_obj.writestr(arc_name + '/', '')
            attr_obj = self.zip_obj.getinfo(arc_name + '/')
            attr_obj.external_attr = stat.S_IMODE(os.stat(file_name)[0]) << 16
            for f in os.listdir(file_name):
                self.add_to_archive(os.path.join(file_name, f),
                                    os.path.join(arc_name, f))
        else:
            if os.path.islink(file_name):
                dest = os.readlink(file_name)
                attr = zipfile.ZipInfo()
                attr.filename = file_name
                attr.create_system = 3
                attr.external_attr = self.symmagic
                self.zip_obj.writestr(attr, dest)
            else:
                comp_type = zipfile.ZIP_DEFLATED
                if file_name.endswith((ctx.const.lzma_suffix,
                                       ctx.const.xz_suffix)):
                    comp_type = zipfile.ZIP_STORED
                self.zip_obj.write(file_name, arc_name, comp_type)

                if not arc_name:
                    zinfo = self.zip_obj.getinfo(file_name)
                else:
                    zinfo = self.zip_obj.getinfo(arc_name)
                zinfo.create_system = 3

    def add_basename_to_archive(self, file_name):
        """Add only the basepath to the zip file. For example; if the given
        file_name parameter is /usr/local/bin/somedir, this function
        will create only the base directory/file somedir in the
        archive."""
        cwd = os.getcwd()
        path_name = os.path.dirname(file_name)
        file_name = os.path.basename(file_name)
        if path_name:
            os.chdir(path_name)
        self.add_to_archive(file_name)
        os.chdir(cwd)

    def has_file(self, file_path):
        """ Returns true if file_path is member of the zip archive"""
        return file_path in self.zip_obj.namelist()

    def read_file(self, file_path):
        return self.zip_obj.read(file_path).decode('utf-8')

    def unpack_file_cond(self, pred, target_dir, archive_root=''):
        """Unpack/Extract files according to predicate function
        pred: filename -> bool
        unpacks stuff into target_dir and only extracts files
        from archive_root, treating it as the archive root"""
        zip_obj = self.zip_obj
        for info in zip_obj.infolist():
            if pred(info.filename):  # check if condition holds

                # below code removes that, so we find it here
                is_dir = info.filename.endswith('/')

                # calculate output file name
                if archive_root == '':
                    outpath = info.filename
                else:
                    # change archive_root
                    if util.subpath(archive_root, info.filename):
                        outpath = util.removepathprefix(archive_root,
                                                        info.filename)
                    else:
                        continue  # don't extract if not under

                ofile = os.path.join(target_dir, outpath)

                if is_dir:  # this is a directory
                    if not os.path.isdir(ofile):
                        os.makedirs(ofile)
                        perm = info.external_attr
                        perm &= 0xFFFF0000
                        perm >>= 16
                        perm |= 0x00000100
                        os.chmod(ofile, perm)
                    continue

                # check that output dir is present
                util.ensure_dirs(os.path.dirname(ofile))

                # remove output file we might be overwriting.
                # (also check for islink? for broken symlinks...)
                if os.path.isfile(ofile) or os.path.islink(ofile):
                    os.remove(ofile)

                if info.external_attr == self.symmagic:
                    if os.path.isdir(ofile):
                        # A rare case, the file used to be a dir,
                        # now it is a symlink!
                        shutil.rmtree(ofile)
                    target = zip_obj.read(info.filename)
                    os.symlink(target, ofile)
                else:
                    perm = info.external_attr
                    perm &= 0x08FF0000
                    perm >>= 16
                    perm |= 0x00000100

                    info.filename = outpath
                    zip_obj.extract(info, target_dir)
                    os.chmod(ofile, perm)

    def unpack_files(self, paths, target_dir):
        self.unpack_file_cond(lambda f: f in paths, target_dir)

    def unpack_dir(self, path, target_dir):
        self.unpack_file_cond(lambda f: util.subpath(path, f), target_dir)

    def unpack_dir_flat(self, path, target_dir):
        self.unpack_file_cond(lambda f: util.subpath(path, f),
                              target_dir, path)

    def unpack(self, target_dir, clean_dir=False):
        super(ArchiveZip, self).unpack(target_dir, clean_dir)

        self.unpack_file_cond(lambda f: True, target_dir)
        self.close()
        return

class Archive:
    """Archive is the main factory for ArchiveClasses, regarding the
    Abstract Factory Pattern :)."""

    def __init__(self, file_path, arch_type):
        """Accepted archive types:
        targz, tarbz2, tarlzma, tarxz, tarZ, tar, zip, gzip, bzip2,
        lzma, xz, binary"""

        if not arch_type:
            arch_type = self._guess_archive_type(file_path)

        handlers = {'targz':    ArchiveTar,
                    'tarbz2':   ArchiveTar,
                    'tarlzma':  ArchiveTar,
                    'tarxz':    ArchiveTar,
                    'tarZ':     ArchiveTarZ,
                    'tar':      ArchiveTar,
                    'zip':      ArchiveZip,
                    'gz':       ArchiveGzip,
                    'gzip':     ArchiveGzip,
                    'bz2':      ArchiveBzip2,
                    'bzip2':    ArchiveBzip2,
                    'lzma':     ArchiveLzma,
                    'xz':       ArchiveLzma,
                    '7z':       Archive7Zip,
                    'binary':   ArchiveBinary}

        handler = handlers.get(arch_type)
        if handler is None:
            raise UnknownArchiveType

        self.archive = handler(file_path, arch_type)

    @staticmethod
    def _guess_archive_type(file_path):
        types = (("targz",      (".tar.gz", ".tgz")),
                 ("tarbz2",     (".tar.bz2", ".tar.bz", ".tbz2", ".tbz")),
                 ("tarlzma",    (".tar.lzma", ".tlz")),
                 ("tarxz",      (".tar.xz", ".txz")),
                 ("tarZ",       (".tar.Z",)),
                 ("tar",        (".tar",)),
                 ("zip",        (".zip", ".ZIP")),
                 ("gz",         (".gz",)),
                 ("bz2",        (".bz2", ".bz")),
                 ("lzma",       (".lzma",)),
                 ("xz",         (".xz",)),
                 ("7z",         (".7z",)),
                 ("binary",     (".bin", ".run", ".sh")))

        for _type, extensions in types:
            if file_path.endswith(extensions):
                return _type

        return "binary"

    def unpack(self, target_dir, clean_dir=False):
        self.archive.unpack(target_dir, clean_dir)

    def unpack_files(self, files, target_dir):
        self.archive.unpack_files(files, target_dir)

class SourceArchives:
    """This is a wrapper for supporting multiple SourceArchive objects."""

    def __init__(self, spec):
        self.sourceArchives = [SourceArchive(a) for a in spec.source.archive]

    def fetch(self, interactive=True):
        for archive in self.sourceArchives:
            archive.fetch(interactive)

    def unpack(self, target_dir, clean_dir=True):
        self.sourceArchives[0].unpack(target_dir, clean_dir)
        for archive in self.sourceArchives[1:]:
            archive.unpack(target_dir, clean_dir=False)

class SourceArchive:
    """source archive. this is a class responsible for fetching
    and unpacking a source archive"""

    def __init__(self, archive):
        self.url = inary.uri.URI(archive.uri)
        self.archiveFile = os.path.join(ctx.config.archives_dir(), self.url.filename())
        self.archive = archive
        self.progress = None

    def fetch(self, interactive=True):
        if not self.is_cached(interactive):
            if interactive:
                self.progress = ctx.ui.Progress

            try:
                ctx.ui.info(_("Fetching source from: \"{}\"").format(self.url.uri))
                if self.url.get_uri().startswith("mirrors://"):
                    self.fetch_from_mirror()
                elif self.url.get_uri().startswith("file://") or self.url.get_uri().startswith("/"):
                    self.fetch_from_locale()
                else:
                    inary.fetcher.fetch_url(self.url, ctx.config.archives_dir(), self.progress)
            except inary.fetcher.FetchError:
                if ctx.config.values.build.fallback and not self.is_cached(interactive=False):
                    self.fetch_from_fallback()
                else:
                    raise

            ctx.ui.info(_("Source archive is stored: \"{0}/{1}\"").format(ctx.config.archives_dir(), self.url.filename()))

    def fetch_from_fallback(self):
        inary.fetcher.fetch_url(self.url.get_uri(), ctx.config.archives_dir(), self.progress)

    def fetch_from_locale(self):
        inary.fetcher.fetch_from_locale(self.url.get_uri(), ctx.config.archives_dir(), destfile=self.uri.filename())

    def fetch_from_mirror(self):
        inary.fetcher.fetch_from_mirror(self.url.get_uri(), ctx.config.archives_dir(), self.progress)

    def is_cached(self, interactive=True):
        if not os.access(self.archiveFile, os.R_OK):
            return False

        # check hash
        if util.check_file_hash(self.archiveFile, self.archive.sha1sum) or ctx.get_option('ignore_verify'):
            if interactive and ctx.config.get_option('debug'):
                ctx.ui.info(_('\"{}\" [cached]').format(self.archive.name))
            return True

        return False

    def unpack(self, target_dir, clean_dir=True):

        # check archive file's integrity
        if not util.check_file_hash(self.archiveFile, self.archive.sha1sum):
            ctx.ui.warning(_("{}\n *Expected sha1 value: {} \n* Received sha1 value: {} \n".format(self.url.filename(),self.archive.sha1sum,util.sha1_file(self.archiveFile))))
            if not ctx.get_option('ignore_verify'):
                raise SourceArchiveError(_("unpack: check_file_hash failed."))
            else:
                ctx.ui.warning(_("* Archive verification passed. Such problems may occur during the build process."))
        try:
            archive = Archive(self.archiveFile, self.archive.type)
        except UnknownArchiveType:
            raise SourceArchiveError(
                _("Unknown archive type '{0}' is given for '{1}'.").format(self.archive.type, self.url.filename()))
        except ArchiveHandlerNotInstalled:
            raise SourceArchiveError(
                _("Inary needs \'{}\' to unpack this archive but it is not installed.").format(self.archive.type))

        target_dir = os.path.join(target_dir, self.archive.target or "")
        archive.unpack(target_dir, clean_dir)


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# 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; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.

"""Atomic package operations such as install/remove/upgrade"""

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import os
import shutil
import zipfile

import inary

import inary.configfile
import inary.context as ctx
import inary.data
import inary.errors
import inary.data
import inary.db
import inary.file
import inary.package
import inary.operations
import inary.uri
import inary.ui
import inary.util as util
import inary.version
import inary.reactor

class Error(inary.errors.Error):
    pass

class NotfoundError(inary.errors.Error):
    pass

# single package operations

class AtomicOperation(object):

    def __init__(self, ignore_dep=None):
        # self.package = package
        if ignore_dep is None:
            self.ignore_dep = ctx.config.get_option('ignore_dependency')
        else:
            self.ignore_dep = ignore_dep

        self.historydb = inary.db.historydb.HistoryDB()

    def run(self, package):
        """perform an atomic package operation"""
        pass

# possible paths of install operation
(INSTALL, REINSTALL, UPGRADE, DOWNGRADE, REMOVE) = list(range(5))
opttostr = {INSTALL: "install", REMOVE: "remove", REINSTALL: "reinstall", UPGRADE: "upgrade", DOWNGRADE: "downgrade"}

class Install(AtomicOperation):
    """Install class, provides install routines for inary packages"""

    @staticmethod
    def from_name(name, ignore_dep=None):
        packagedb = inary.db.packagedb.PackageDB()
        # download package and return an installer object
        # find package in repository
        repo = packagedb.which_repo(name)
        if repo:
            repodb = inary.db.repodb.RepoDB()
            ctx.ui.debug(_("Package \"{0}\" found in repository \"{1}\"").format(name, repo))

            repo = repodb.get_repo(repo)
            pkg = packagedb.get_package(name)
            delta = None

            installdb = inary.db.installdb.InstallDB()
            # Package is installed. This is an upgrade. Check delta.
            if installdb.has_package(pkg.name):
                (version, release, build, distro, distro_release) = installdb.get_version_and_distro_release(pkg.name)
                # inary distro upgrade should not use delta support
                if distro == pkg.distribution and distro_release == pkg.distributionRelease:
                    delta = pkg.get_delta(release)

            ignore_delta = ctx.config.values.general.ignore_delta

            # If delta exists than use the delta uri.
            if delta and not ignore_delta:
                pkg_uri = delta.packageURI
                pkg_hash = delta.packageHash
            else:
                pkg_uri = pkg.packageURI
                pkg_hash = pkg.packageHash

            uri = inary.uri.URI(pkg_uri)
            if uri.is_absolute_path():
                pkg_path = str(pkg_uri)
            else:
                pkg_path = os.path.join(os.path.dirname(repo.indexuri.get_uri()),
                                        str(uri.path()))

            ctx.ui.info(_("Package URI: \"{}\"").format(pkg_path), verbose=True)

            # Bug 4113
            cached_file = inary.package.Package.is_cached(pkg_path)
            if cached_file and util.sha1_file(cached_file) != pkg_hash:
                os.unlink(cached_file)
                cached_file = None

            install_op = Install(pkg_path, ignore_dep)

            # Bug 4113
            if not cached_file:
                downloaded_file = install_op.package.filepath
                if util.sha1_file(downloaded_file) != pkg_hash:
                    raise inary.errors.Error(_("Download Error: Package does not match the repository package."))

            return install_op
        else:
            raise Error(_("Package \"{}\" not found in any active repository.").format(name))

    def __init__(self, package_fname, ignore_dep=None, ignore_file_conflicts=None):
        if not ctx.filesdb: ctx.filesdb = inary.db.filesdb.FilesDB()
        "initialize from a file name"
        super(Install, self).__init__(ignore_dep)
        if not ignore_file_conflicts:
            ignore_file_conflicts = ctx.get_option('ignore_file_conflicts')
        self.ignore_file_conflicts = ignore_file_conflicts
        self.package_fname = package_fname
        try:
            self.package = inary.package.Package(package_fname)
            self.package.read()
        except zipfile.BadZipfile:
            raise zipfile.BadZipfile(self.package_fname)
        self.metadata = self.package.metadata
        self.files = self.package.files
        self.pkginfo = self.metadata.package
        self.installedSize = self.metadata.package.installedSize
        self.installdb = inary.db.installdb.InstallDB()
        self.operation = INSTALL
        self.store_old_paths = None

    def install(self, ask_reinstall=True):

        # Any package should remove the package it replaces before
        self.check_replaces()
        ctx.ui.status(_("Installing \"{0.name}\", version {0.version}, release {0.release}").format(self.pkginfo), push_screen=False)
        ctx.ui.notify(inary.ui.installing, package=self.pkginfo, files=self.files)

        self.ask_reinstall = ask_reinstall
        self.check_versioning(self.pkginfo.version, self.pkginfo.release)
        self.check_relations()
        self.check_operation()

        self.extract_install()
        self.store_inary_files()
        self.update_databases()

        ctx.ui.close()
        if self.operation == UPGRADE:
            event = inary.ui.upgraded
        else:
            event = inary.ui.installed
        ctx.ui.notify(event, package=self.pkginfo, files=self.files)
        util.xterm_title_reset()

    def check_requirements(self):
        """check system requirements"""
        # Check free space
        total_size, symbol = util.human_readable_size(util.free_space())
        if util.free_space() < self.installedSize:
            raise Error(_("Is there enought free space in your disk."))
        ctx.ui.info(_("Free space in \'destinationdirectory\': {:.2f} {} ".format(total_size, symbol)), verbose=True)

    def check_replaces(self):
        for replaced in self.pkginfo.replaces:
            if self.installdb.has_package(replaced.package):
                inary.operations.remove.remove_replaced_packages([replaced.package])

    @staticmethod
    def check_versioning(version, release):
        try:
            int(release)
            inary.version.make_version(version)
        except (ValueError, inary.version.InvalidVersionError):
            raise Error(_("{0}-{1} is not a valid INARY version format").format(version, release))

    def check_relations(self):
        # check dependencies
        # Fixme: !!!
        # if not ctx.config.get_option('ignore_dependency'):
        #    if not self.pkginfo.installable():
        #        raise Error(_("{} package cannot be installed unless the dependencies are satisfied").format(self.pkginfo.name))

        # If it is explicitly specified that package conflicts with this package and also
        # we passed check_conflicts tests in operations.py than this means a non-conflicting
        # pkg is in "order" to be installed that has no file conflict problem with this package.
        # PS: we need this because "order" generating code does not consider conflicts.
        def really_conflicts(pkg):
            if not self.pkginfo.conflicts:
                return True

            return not pkg in [x.package for x in self.pkginfo.conflicts]

        # check file conflicts
        file_conflicts = []
        for f in self.files.list:
            pkg, existing_file = ctx.filesdb.get_file(f.path)
            if pkg:
                dst = util.join_path(ctx.config.dest_dir(), f.path)
                if pkg != self.pkginfo.name and not os.path.isdir(dst) and really_conflicts(pkg):
                    file_conflicts.append((pkg, existing_file))
        if file_conflicts:
            file_conflicts_str = ""
            for (pkg, existing_file) in file_conflicts:
                file_conflicts_str += _("\"/{0}\" from \"{1}\" package\n").format(existing_file, pkg)
            msg = _('File conflicts:\n\"{}\"').format(file_conflicts_str)
            if self.ignore_file_conflicts:
                ctx.ui.warning(msg)
            else:
                raise Error(msg)

    def check_operation(self):

        self.old_pkginfo = None
        pkg = self.pkginfo

        if self.installdb.has_package(pkg.name):  # is this a reinstallation?
            (iversion_s, irelease_s, ibuild) = self.installdb.get_version(pkg.name)

            # determine if same version
            if pkg.release == irelease_s:
                if self.ask_reinstall:
                    if not ctx.ui.confirm(_('Re-install same version package?')):
                        raise Error(_('Package re-install declined'))
                self.operation = REINSTALL
            else:
                pkg_version = inary.version.make_version(pkg.version)
                iversion = inary.version.make_version(iversion_s)
                if ctx.get_option('store_lib_info') and pkg_version > iversion:
                    self.store_old_paths = os.path.join(ctx.config.old_paths_cache_dir(), pkg.name)
                    ctx.ui.info(_('Storing old paths info.'))
                    open(self.store_old_paths, "w").write("Version: {}\n".format(iversion_s))

                pkg_release = int(pkg.release)
                irelease = int(irelease_s)

                # is this an upgrade?
                # determine and report the kind of upgrade: version, release
                if pkg_version > iversion:
                    ctx.ui.info(_('Upgrading to new upstream version.'))
                    self.operation = UPGRADE
                elif pkg_release > irelease:
                    ctx.ui.info(_('Upgrading to new distribution release.'))
                    self.operation = UPGRADE

                # is this a downgrade? confirm this action.
                if not self.operation == UPGRADE:
                    if pkg_version < iversion:
                        # x = _('Downgrade to old upstream version?')
                        x = None
                    elif pkg_release < irelease:
                        x = _('Downgrade to old distribution release?')
                    else:
                        x = None
                    if self.ask_reinstall and x and not ctx.ui.confirm(x):
                        raise Error(_('Package downgrade declined'))
                    self.operation = DOWNGRADE

            # schedule for reinstall
            self.old_files = self.installdb.get_files(pkg.name)
            self.old_pkginfo = self.installdb.get_info(pkg.name)
            self.old_path = self.installdb.pkg_dir(pkg.name, iversion_s, irelease_s)
            self.remove_old = Remove(pkg.name, store_old_paths=self.store_old_paths)

    def reinstall(self):
        return not self.operation == INSTALL

    def preInstall(self):
        self.config_later = ctx.scom and not ctx.get_option("ignore_scom")
        if self.metadata.package.providesScom:
            if self.config_later:
                try:
                    if self.operation == UPGRADE or self.operation == DOWNGRADE:
                        fromVersion = self.old_pkginfo.version
                        fromRelease = self.old_pkginfo.release
                    else:
                        fromVersion = 0
                        fromRelease = 1
                    ctx.ui.notify(inary.ui.configuring, package=self.pkginfo, files=self.files)
                    inary.reactor.pre_install(
                        self.pkginfo.name,
                        self.metadata.package.providesScom,
                        self.package.scom_dir(),
                        os.path.join(self.package.pkg_dir(), ctx.const.metadata_xml),
                        os.path.join(self.package.pkg_dir(), ctx.const.files_xml),
                        fromVersion,
                        fromRelease,
                        self.metadata.package.version,
                        self.metadata.package.release
                    )
                    ctx.ui.notify(inary.ui.configured, package=self.pkginfo, files=self.files)
                except :
                    ctx.ui.warning(_('Configuration of \"{}\" package failed.').format(self.pkginfo.name))

    def postInstall(self):
        self.config_later = ctx.scom and not ctx.get_option("ignore_scom")
        if self.metadata.package.providesScom:
            if self.config_later:
                try:
                    if self.operation == UPGRADE or self.operation == DOWNGRADE:
                        fromVersion = self.old_pkginfo.version
                        fromRelease = self.old_pkginfo.release
                    else:
                        fromVersion = 0
                        fromRelease = 1
                    ctx.ui.notify(inary.ui.configuring, package=self.pkginfo, files=self.files)
                    inary.reactor.post_install(
                        self.pkginfo.name,
                        self.metadata.package.providesScom,
                        self.package.scom_dir(),
                        os.path.join(self.package.pkg_dir(), ctx.const.metadata_xml),
                        os.path.join(self.package.pkg_dir(), ctx.const.files_xml),
                        fromVersion,
                        fromRelease,
                        self.metadata.package.version,
                        self.metadata.package.release
                    )
                    ctx.ui.notify(inary.ui.configured, package=self.pkginfo, files=self.files)
                except :
                    ctx.ui.warning(_('Configuration of \"{}\" package failed.').format(self.pkginfo.name))

    def extract_install(self):
        """unzip package in place"""

        ctx.ui.notify(inary.ui.extracting, package=self.pkginfo, files=self.files)

        # Package file's path may not be relocated or content may not be changed but
        # permission may be changed
        def update_permissions():
            permissions = inary.operations.delta.find_permission_changes(self.old_files, self.files)
            for path, mode in permissions:
                os.chmod(path, mode)

        # Delta package does not contain the files that have the same hash as in
        # the old package's. Because it means the file has not changed. But some
        # of these files may be relocated to some other directory in the new package.
        # We handle these cases here.
        def relocate_files():
            missing_old_files = set()

            for old_file, new_file in inary.operations.delta.find_relocations(self.old_files, self.files):
                old_path = os.path.join(ctx.config.dest_dir(), old_file.path)
                new_path = os.path.join(ctx.config.dest_dir(), new_file.path)

                if not os.path.lexists(old_path):
                    missing_old_files.add(old_path)
                    continue

                if os.path.lexists(new_path):
                    # If one of the parent directories is a symlink, it is possible
                    # that the new and old file paths refer to the same file.
                    # In this case, there is nothing to do here.
                    #
                    # e.g. /lib/libdl.so and /lib64/libdl.so when /lib64 is
                    # a symlink to /lib.
                    if os.path.basename(old_path) == os.path.basename(new_path) \
                            and os.path.samestat(os.lstat(old_path), os.lstat(new_path)):
                        continue

                    os.unlink(new_path)

                destdir = os.path.dirname(new_path)
                if not os.path.exists(destdir):
                    os.makedirs(destdir)

                if os.path.islink(old_path):
                    os.symlink(os.readlink(old_path), new_path)
                else:
                    shutil.copy(old_path, new_path)

            if missing_old_files:
                ctx.ui.warning(
                    _("Unable to relocate following files. Reinstallation of this package is strongly recommended."))
                for f in sorted(missing_old_files):
                    ctx.ui.warning("    - {}".format(f))

        # remove left over files from the old package.
        def clean_leftovers():
            stat_cache = {}

            files_by_name = {}
            new_paths = []
            for f in self.files.list:
                files_by_name.setdefault(os.path.basename(f.path), []).append(f)
                new_paths.append(f.path)

            for old_file in self.old_files.list:
                if old_file.path in new_paths:
                    continue

                old_file_path = os.path.join(ctx.config.dest_dir(), old_file.path)

                try:
                    old_file_stat = os.lstat(old_file_path)
                except OSError:
                    continue

                old_filename = os.path.basename(old_file.path)

                # If one of the parent directories is a symlink, it is possible
                # that the new and old file paths refer to the same file.
                # In this case, we must not remove the old file.
                #
                # e.g. /lib/libdl.so and /lib64/libdl.so when /lib64 is
                # a symlink to /lib.
                for new_file in files_by_name.get(old_filename, []):

                    new_file_stat = stat_cache.get(new_file.path)

                    if new_file_stat is None:
                        path = os.path.join(ctx.config.dest_dir(), new_file.path)
                        try:
                            new_file_stat = os.lstat(path)
                        except OSError:
                            continue

                        stat_cache[new_file.path] = new_file_stat

                    if os.path.samestat(new_file_stat, old_file_stat):
                        break
                else:
                    Remove.remove_file(old_file, self.pkginfo.name, store_old_paths=self.store_old_paths)

        if self.package_fname.endswith(ctx.const.delta_package_suffix):
            relocate_files()
            update_permissions()

        if self.reinstall():
            clean_leftovers()

        self.package.extract_install(ctx.config.dest_dir())

    def store_inary_files(self):
        """put files.xml, metadata.xml, scom scripts
        somewhere in the file system. We'll need these in future..."""

        if self.reinstall():
            util.clean_dir(self.old_path)

        self.package.extract_file_synced(ctx.const.files_xml, self.package.pkg_dir())
        self.package.extract_file_synced(ctx.const.metadata_xml, self.package.pkg_dir())

        for pscom in self.metadata.package.providesScom:
            fpath = os.path.join(ctx.const.scom_dir, pscom.script)
            # comar prefix is added to the pkg_dir while extracting comar
            # script file. so we'll use pkg_dir as destination.
            ctx.ui.info(_('Storing files of \"{}\" package.').format(fpath), verbose=True)
            self.package.extract_file_synced(fpath, self.package.pkg_dir())

    def update_databases(self):
        """update databases"""
        if self.reinstall():
            self.remove_old.remove_db()

        if (ctx.scom and not ctx.get_option("ignore_scom"))==False:
            if self.pkginfo.providesScom:
                self.installdb.mark_pending(self.pkginfo.name)

        # need service or system restart?
        if self.installdb.has_package(self.pkginfo.name):
            (version, release, build) = self.installdb.get_version(self.pkginfo.name)
            actions = self.pkginfo.get_update_actions(release)
        else:
            actions = self.pkginfo.get_update_actions("1")

        for package_name in actions.get("serviceRestart", []):
            inary.db.installdb.InstallDB().mark_needs_restart(package_name)

        for package_name in actions.get("systemRestart", []):
            inary.db.installdb.InstallDB().mark_needs_reboot(package_name)

        # filesdb
        if ctx.get_option('debug'):
            ctx.ui.info(_('Adding files of \"{}\" package to database...').format(self.metadata.package.name), color='faintpurple')
        ctx.filesdb.add_files(self.metadata.package.name, self.files)

        # installed packages
        self.installdb.add_package(self.pkginfo)

        otype = "delta" if self.package_fname.endswith(ctx.const.delta_package_suffix) else None
        self.historydb.add_and_update(pkgBefore=self.old_pkginfo, pkgAfter=self.pkginfo,
                                      operation=opttostr[self.operation], otype=otype)

def install_single(pkg, upgrade=False):
    """install a single package from URI or ID"""
    url = inary.uri.URI(pkg)
    # Check if we are dealing with a remote file or a real path of
    # package filename. Otherwise we'll try installing a package from
    # the package repository.
    if url.is_remote_file() or os.path.exists(url.uri):
        install_single_file(pkg, upgrade)
    else:
        install_single_name(pkg, upgrade)

# FIXME: Here and elsewhere pkg_location must be a URI
def install_single_file(pkg_location, upgrade=False):
    """install a package file"""
    Install(pkg_location).install(not upgrade)

def install_single_name(name, upgrade=False):
    """install a single package from ID"""
    Install.from_name(name).install(not upgrade)

class Remove(AtomicOperation):

    def __init__(self, package_name, ignore_dep=None, store_old_paths=None):
        if not ctx.filesdb: ctx.filesdb = inary.db.filesdb.FilesDB()
        super(Remove, self).__init__(ignore_dep)
        self.installdb = inary.db.installdb.InstallDB()
        self.package_name = package_name
        self.package = self.installdb.get_package(self.package_name)
        self.store_old_paths = store_old_paths
        try:
            self.files = self.installdb.get_files(self.package_name)
        except inary.errors.Error as e:
            # for some reason file was deleted, we still allow removes!
            ctx.ui.error(str(e))
            ctx.ui.warning(_('File list could not be read for \"{}\" package, continuing removal.').format(package_name))
            self.files = inary.data.files.Files()

    def run(self):
        """Remove a single package"""
        ctx.ui.notify(inary.ui.removing, package=self.package, files=self.files)
        ctx.ui.debug(_("Removing \"{0.name}\", version {0.version}, release {0.release}").format(self.package))

        if not self.installdb.has_package(self.package_name):
            raise Exception(_('Trying to remove nonexistent package ')
                            + self.package_name)

        self.check_dependencies()

        for fileinfo in self.files.list:
            self.remove_file(fileinfo, self.package_name, True)

        self.update_databases()

        self.remove_inary_files()
        ctx.ui.close()
        ctx.ui.notify(inary.ui.removed, package=self.package, files=self.files)
        util.xterm_title_reset()

    def check_dependencies(self):
        # FIXME: why is this not implemented? -- exa
        # we only have to check the dependencies to ensure the
        # system will be consistent after this removal
        pass
        # is there any package who depends on this package?

    @staticmethod
    def remove_file(fileinfo, package_name, remove_permanent=False, store_old_paths=None):

        if fileinfo.permanent and not remove_permanent:
            return

        fpath = util.join_path(ctx.config.dest_dir(), fileinfo.path)

        historydb = inary.db.historydb.HistoryDB()
        # we should check if the file belongs to another
        # package (this can legitimately occur while upgrading
        # two packages such that a file has moved from one package to
        # another as in #2911)
        pkg, existing_file = ctx.filesdb.get_file(fileinfo.path)
        if pkg and pkg != package_name:
            ctx.ui.warning(_('Not removing conflicted file : \"{}\"').format(fpath))
            return

        if fileinfo.type == ctx.const.conf:
            # config files are precious, leave them as they are
            # unless they are the same as provided by package.
            # remove symlinks as they are, cause if the hash of the
            # file it links has changed, it will be kept as is,
            # and when the package is reinstalled the symlink will
            # link to that changed file again.
            try:
                if os.path.islink(fpath) or util.sha1_file(fpath) == fileinfo.hash:
                    os.unlink(fpath)
                else:
                    # keep changed file in history
                    historydb.save_config(package_name, fpath)

                    # after saving to history db, remove the config file any way
                    if ctx.get_option("purge"):
                        os.unlink(fpath)
            except util.FileError:
                pass
        else:
            if os.path.isfile(fpath) or os.path.islink(fpath):
                os.unlink(fpath)
                if store_old_paths:
                    open(store_old_paths, "a").write("{}\n".format(fpath))
            elif os.path.isdir(fpath) and not os.listdir(fpath):
                os.rmdir(fpath)
            else:
                ctx.ui.warning(
                    _('Installed file \"{}\" does not exist on system [Probably you manually deleted]').format(fpath))
                return

        # remove emptied directories
        dpath = os.path.dirname(fpath)
        while dpath != '/' and not os.listdir(dpath):
            os.rmdir(dpath)
            dpath = os.path.dirname(dpath)

    def run_preremove(self):
        if self.package.providesScom:
            if ctx.scom and not ctx.get_option("ignore_scom"):
                inary.reactor.pre_remove(
                         self.package_name,
                         os.path.join(self.package.pkg_dir(), ctx.const.metadata_xml),
                         os.path.join(self.package.pkg_dir(), ctx.const.files_xml),
                         provided_scripts=self.package.providesScom,
                         )

    def update_databases(self):
        self.remove_db()
        self.historydb.add_and_update(pkgBefore=self.package, operation="remove")

    def remove_inary_files(self):
        if ctx.get_option('debug'):
            ctx.ui.info(_('Removing files of \"{}\" package from system...').format(self.package_name), color='faintpurple')
        util.clean_dir(self.package.pkg_dir())

    def remove_db(self):
        if ctx.get_option('debug'):
            ctx.ui.info(_('Removing files of \"{}\" package from database...').format(self.package_name), color='faintyellow')
        self.installdb.remove_package(self.package_name)
        ctx.filesdb.remove_files(self.files.list)

# FIX:DB
#         # FIXME: something goes wrong here, if we use ctx operations ends up with segmentation fault!
#         inary.db.packagedb.remove_tracking_package(self.package_name)

def remove_single(package_name):
    Remove(package_name).run_preremove()
    Remove(package_name).run()


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# 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; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

import fnmatch
import os

import inary.db

def exclude_from(packages, exfrom):
    if not os.path.exists(exfrom):
        return packages

    patterns = []
    if os.path.exists(exfrom):
        for line in open(exfrom).readlines():
            if not line.startswith('#') and not line == '\n':
                patterns.append(line.strip())
        if patterns:
            return exclude(packages, patterns)

    return packages

def exclude(packages, patterns):
    packages = set(packages)
    componentdb = inary.db.componentdb.ComponentDB()

    for pattern in patterns:
        # match pattern in package names
        match = fnmatch.filter(packages, pattern)
        packages -= set(match)

        if not match:
            # match pattern in component names
            for compare in fnmatch.filter(componentdb.list_components(), pattern):
                packages -= set(componentdb.get_union_packages(compare, walk=True))

    return list(packages)


code:
Writing commands:
-----------------

TODO: this is a *bit* out of date now.

Subclass from Command.

Be careful not to import INARY modules before Command is run, e.g.
do it in run() method. This is necessary to prevent an obscure
initialization error.

We used to import inary.operations in inary.cli

This prevented inary.operations from having the current global
inary.ui.ui because it's loaded before the UI is set! So, despite
what you might understand from the python FAQ 1.2.3
http://www.python.org/doc/faq/programming.html#how-do-i-share-global-variables-across-modules
you have to be careful about the initialization order when using
singleton modules.

Another way to solve this is to ensure that the global module is
always imported again in every function it is called (see
ui.CLI.confirm() ) but this is more expensive.

path: inary/cli/README


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# 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; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

import re
import sys
import tty
import locale

import inary.ui
import inary.errors
import inary.context as ctx
import inary.util as util

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

class Error(inary.errors.Error):
    pass

class Exception(inary.errors.Exception):
    pass

# in old releases used this printu function
def printu(obj, err=False):
    if not isinstance(obj, str):
        obj = str(obj)
    if err:
        out = sys.stderr
    else:
        out = sys.stdout
    out.write(str(obj))
    out.flush()

class CLI(inary.ui.UI):
    """Command Line Interface"""

    def __init__(self, show_debug=False, show_verbose=False):
        super(CLI, self).__init__(show_debug, show_verbose)
        self.warnings = 0
        self.errors = 0

    def close(self):
        util.xterm_title_reset()

    def output(self, msg, err=False, verbose=False):
        if (verbose and self.show_verbose) or (not verbose):
            if isinstance(msg, bytes):
                msg = msg.decode('utf-8')
            if err:
                sys.stderr.write(str(msg))
            else:
                sys.stdout.write(str(msg))

    def formatted_output(self, msg, verbose=False, noln=False, column=":"):
        key_width = 20
        line_format = "%(key)-20s%(column)s%(rest)s"
        term_height, term_width = util.get_terminal_size()

        def find_whitespace(s, i):
            while s[i] not in (" ", "\t"):
                i -= 1
            return i

        def align(s):
            align_width = term_width - key_width - 2
            s_width = len(s)
            new_s = ""
            index = 0
            while True:
                next_index = index + align_width
                if next_index >= s_width:
                    new_s += s[index:]
                    break
                next_index = find_whitespace(s, next_index)
                new_s += s[index:next_index]
                index = next_index
                if index < s_width:
                    new_s += "\n" + " " * (key_width + 1)
            return new_s

        new_msg = ""
        for line in msg.split("\n"):
            key, _column, rest = line.partition(column)
            rest = align(rest)
            new_msg += line_format % {"key": key,
                                      "column": _column,
                                      "rest": rest}
            if not noln:
                new_msg = "{}\n".format(new_msg)
        msg = new_msg
        self.output(str(msg), verbose=verbose)

    def info(self, msg, verbose=False, noln=False, color='default'):
        # TODO: need to look at more kinds of info messages
        # let's cheat from KDE :)
        msg = util.colorize(msg, color)
        if verbose:
            msg = util.colorize(_('Verboses: '), 'brightwhite') + msg
        if not noln:
            msg = '{}\n'.format(msg)

        self.output(str(msg), verbose=verbose)

    def warning(self, msg, verbose=False):
        msg = str(msg)
        self.warnings += 1
        if ctx.log:
            ctx.log.warning(msg)
        if ctx.get_option('no_color'):
            self.output(_('Warning: ') + msg + '\n', err=True, verbose=verbose)
        else:
            self.output(util.colorize(msg + '\n', 'brightyellow'), err=True, verbose=verbose)

    def error(self, msg):
        msg = str(msg)
        self.errors += 1
        if ctx.log:
            ctx.log.error(msg)
        if ctx.get_option('no_color'):
            self.output(_('Error: ') + msg + '\n', err=True)
        else:
            self.output(util.colorize(msg + '\n', 'brightred'), err=True)

    def action(self, msg, verbose=False):
        # TODO: this seems quite redundant?
        msg = str(msg)
        if ctx.log:
            ctx.log.info(msg)
        self.output(util.colorize(msg + '\n', 'green'))

    def choose(self, msg, opts):
        msg = str(msg)
        endmsg = _('\n Select one:')
        prompt = ""
        for opt in opts:
            prompt += util.colorize('[  {}  ]\n'.format(opt), 'faintblue')

        while True:
            s = input(prompt)
            for opt in opts:
                if opt.startswith(str(s)):
                    return opt

    def confirm(self, msg,invert=False):
        msg = str(msg)
        if ctx.config.options and ctx.config.options.yes_all:
            return True

        locale.setlocale(locale.LC_ALL, "")
        yes_expr = re.compile(locale.nl_langinfo(locale.YESEXPR))
        no_expr = re.compile(locale.nl_langinfo(locale.NOEXPR))
        locale.setlocale(locale.LC_ALL, "C")

        tty.tcflush(sys.stdin.fileno(), 0)
        if invert:
            prompt = msg + util.colorize(" "+_('(yes'), 'red') + '/' + util.colorize(_('no)'), 'green') + ":  "
        else:
            prompt = msg + util.colorize(" "+_('(yes'), 'green') + '/' + util.colorize(_('no)'), 'red') + ":  "
        util.noecho(False)
        s = input(prompt)
        util.noecho(True)

        if yes_expr.search(s):
            return True
        else:
            return False

    def display_progress(self, **ka):
        """ display progress of any operation """

        if ka['operation'] in ["removing", "rebuilding-db"]:
            return

        elif ka['operation'] == "fetching":
            if not ctx.get_option("no_color"):
                complated_background = 'backgroundgreen'
                queried_background = 'backgroundyellow'
                complated='brightgreen'
            else:
                complated_background = queried_background = complated = "default"

            hr_size, hr_symbol = util.human_readable_size(ka["total_size"])
            phr_size, phr_symbol = util.human_readable_size(ka["downloaded_size"])
            totalsize = '{:.1f} {}'.format(hr_size, hr_symbol)
            nowsize = '{:.1f} {}'.format(phr_size, phr_symbol)

            file_and_totalsize = '[{:.40} {}/{}]'.format(ka['basename'],nowsize,totalsize)
            percentage_and_time = '{:.2f} {}'.format(ka['rate'],ka['symbol'])

            term_rows, term_columns = util.get_terminal_size()
            spacenum = (term_columns - ( len(file_and_totalsize) + len(percentage_and_time) ) )
            spacenum = spacenum - 10
            if spacenum < 1:
                spacenum = 0

            msg = file_and_totalsize + ' ' * spacenum + percentage_and_time

            if len(msg) < 1:
                self.output(out)

            lmsg = int( ( len(msg) * ka["percent"] ) / 100 ) + 1
            # \r\x1b[2K erase current line
            if ka["percent"] == 100:
                if ctx.get_option('debug'):
                    self.output("\r\x1b[2K" + ctx.const.colors[complated] + ka['basename'] +" [" + _("downloaded")+"]"+ctx.const.colors['default'])
                else:
                    self.output("\r\x1b[2K\x1b[1A") # clear line and move up
            else:
                self.output("\r\x1b[2K" + ctx.const.colors[complated_background] + "{:6.2f}% ".format(ka['percent']) + \
                            msg[:lmsg] + ctx.const.colors[queried_background] + msg[lmsg:] + \
                            ctx.const.colors['default'])
            util.xterm_title("{} ( {:.2f} % )".format(ka['filename'], ka['percent']))

        else:
            self.output("\r{} ( {:.2f} % )".format(ka['info'], ka['percent']))

            util.xterm_title("{} ( {:.2f} % )".format(ka['info'], ka['percent']))

    def status(self, msg=None, push_screen=True):
        if msg:
            msg = str(msg)
            if push_screen:
                self.output(util.colorize(msg + '\n', 'brightgreen'))
            util.xterm_title(msg)

    def notify(self, event, logging=True, **keywords):
        is_debug=False
        if event == inary.ui.installed:
            msg = _('Installed \"{}\"').format(keywords['package'].name)
            color = 'brightgreen'
            is_debug=True
        elif event == inary.ui.installing:
            msg = _('Installing \"{0.name}\", version {0.version}, release {0.release}').format(keywords['package'])
            color = 'faintblue'
            is_debug=True
        elif event == inary.ui.removed:
            msg = _('Removed \"{}\"').format(keywords['package'].name)
            color = 'brightgreen'
        elif event == inary.ui.removing:
            msg = _('Removing \"{}\"').format(keywords['package'].name)
            color = 'brightpurple'
            is_debug=True
        elif event == inary.ui.upgraded:
            msg = _('Upgraded \"{}\"').format(keywords['package'].name)
            color = 'brightgreen'
        elif event == inary.ui.configured:
            msg = _('Configured \"{}\"').format(keywords['package'].name)
            color = 'brightgreen'
        elif event == inary.ui.configuring:
            msg = _('Configuring \"{}\" package.').format(keywords['package'].name)
            color = 'faintyellow'
            is_debug=True
        elif event == inary.ui.extracting:
            msg = _('Extracting the files of \"{}\"').format(keywords['package'].name)
            color = 'faintgreen'
            is_debug=True
        elif event == inary.ui.updatingrepo:
            msg = _('Updating package repository: \"{}\"').format(keywords['name'])
            color = 'green'
            is_debug=True
        elif event == inary.ui.cached:
            total_size, total_symbol = util.human_readable_size(keywords['total'])
            cached_size, cached_symbol = util.human_readable_size(keywords['cached'])
            msg = _('Total size of package(s): {:.2f} {} / {:.2f} {}').format(cached_size,
                                                                              cached_symbol,
                                                                              total_size,
                                                                              total_symbol)
            color = 'cyan'
        elif event == inary.ui.packagestogo:
            if ctx.log:
                ctx.log.info(_("Following packages ordered for process: {}").format(keywords['order']))
            msg = None

        elif event == inary.ui.desktopfile:
            if ctx.log:
                ctx.log.info(_("Extracted desktop file \"{}\"").format(keywords['desktopfile']))
            msg = None

        else:
            msg = None

        if msg:
            if ((not is_debug) or ctx.get_option('debug')):
                self.output(util.colorize(msg + '\n', color))
                if ctx.log and logging:
                    ctx.log.info(msg)


code:
# -*- coding:utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# 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; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

import optparse

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import inary.operations.repository as repository
import inary.cli.command as command
import inary.context as ctx
import inary.db
import inary.errors

class AddRepo(command.Command, metaclass=command.autocommand):
    __doc__ = _("""Add a repository

Usage: add-repo <repo> <indexuri>

<repo>: name of repository to add
<indexuri>: URI of index file

NB: We support only local files (e.g., /a/b/c) and http:// URIs at the moment
""")

    def __init__(self, args):
        super(AddRepo, self).__init__(args)
        self.repodb = inary.db.repodb.RepoDB()

    name = ("add-repo", "ar")

    def options(self):

        group = optparse.OptionGroup(self.parser, _("add-repo options"))
        group.add_option("--ignore-check", action="store_true", default=False,
                         help=_("Ignore repository distribution check"))
        group.add_option("--no-fetch", action="store_true", default=False,
                         help=_("Does not fetch repository index and does not check distribution match"))
        group.add_option("--at", action="store",
                         type="int", default=None,
                         help=_("Add repository at given position (0 is first)"))
        self.parser.add_option_group(group)

    @staticmethod
    def warn_and_remove(message, repo):
        ctx.ui.warning(message)
        repository.remove_repo(repo)

    def run(self):

        if len(self.args) == 2:
            self.init()
            name, indexuri = self.args
            self.just_add = False

            if ctx.get_option('no_fetch'):
                if ctx.ui.confirm(_('Add \"{}\" repository without updating the database?\nBy confirming '
                                        'this you are also adding the repository to your system without '
                                        'checking the distribution of the repository.\n'
                                        'Would you like to continue?').format(name)):
                    self.just_add=True

            if indexuri.endswith(".xml.xz") or indexuri.endswith(".xml"):
                repository.add_repo(name, indexuri, ctx.get_option('at'))
                if not self.just_add:
                    try:
                        repository.update_repos([name])
                    except (inary.errors.Error, IOError) as e:
                        ctx.ui.warning(_("{0} repository could not be reached: {1}").format(name, e))
                        self.warn_and_remove(_("Removing {0} from system.").format(name), name)
                else:
                    ctx.ui.warning(_("Couldn't trust \"{0}\" repository. It is deactivated.").format(name))
                    repository.set_repo_activity(name, False)

            else:
                raise Exception(_("Extension of repository URI must be \".xml.xz\" or \".xml\"."))

        else:
            self.help()
            return


code:
# -*- coding:utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C)  2017 - 2018,  Suleyman POYRAZ (Zaryob)
#
# 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; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

import optparse

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import inary.cli.command as command
import inary.context as ctx
import inary.db

class Blame(command.Command, metaclass=command.autocommand):
    __doc__ = _("""Information about the package owner and release

Usage: blame <package> ... <package>

""")

    def __init__(self, args=None):
        super(Blame, self).__init__(args)
        self.installdb = inary.db.installdb.InstallDB()

    name = ("blame", "bl")

    def options(self):
        group = optparse.OptionGroup(self.parser, _("blame options"))
        group.add_option("-r", "--release", action="store", type="int", help=_("Blame for the given release"))
        group.add_option("-a", "--all", action="store_true", default=False,
                         help=_("Blame for all of the releases"))
        self.parser.add_option_group(group)

    def run(self):
        self.init(database=False, write=False)

        if not self.args:
            self.help()
            return

        for package in self.args:
            if self.installdb.has_package(package):
                pkg = self.installdb.get_package(package)
                release = ctx.get_option('release')
                if not release and not ctx.get_option('all'):
                    self.print_package_info(pkg)
                elif ctx.get_option('all'):
                    for hno, update in enumerate(pkg.history):
                        self.print_package_info(pkg, hno)
                else:
                    for hno, update in enumerate(pkg.history):
                        if int(update.release) == release:
                            self.print_package_info(pkg, hno)
                            return

    @staticmethod
    def print_package_info(package, hno=0):
        s = _('Name: {0}, version: {1}, release: {2}\n').format(
            package.name, package.history[hno].version, package.history[hno].release)
        s += _('Package Maintainer: {0} <{1}>\n').format(str(package.source.packager.name),
                                                         package.source.packager.email)
        s += _('Release Updater: {0.name} <{0.email}>\n').format(package.history[hno])
        s += _('Update Date: {}\n').format(package.history[hno].date)
        s += '\n{}\n'.format(package.history[hno].comment)
        ctx.ui.info(s)


code:
# -*- coding:utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# 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; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

import optparse

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import inary.cli.command as command
import inary.context as ctx
import inary.package
import inary.operations

usage = _("""Build INARY packages

Usage: build [<pspec.xml> | <sourcename>] ...

You can give a URI of the pspec.xml file. INARY will
fetch all necessary files and build the package for you.

Alternatively, you can give the name of a source package
to be downloaded from a repository containing sources.
""")

class Build(command.Command, metaclass=command.autocommand):
    __doc__ = usage

    def __init__(self, args):
        super(Build, self).__init__(args)
        self.scom = True

    name = ("build", "bi")

    def options(self):
        group = optparse.OptionGroup(self.parser, _("build options"))

        group.add_option("-q", "--quiet",
                         action="store_true",
                         default=False,
                         help=_("Run inary build operation without printing "
                                "extra debug information."))

        group.add_option("--ignore-dependency",
                         action="store_true",
                         default=False,
                         help=_("Do not take dependency information into "
                                "account."))

        group.add_option("-O", "--output-dir",
                         action="store",
                         default=None,
                         help=_("Output directory for produced packages."))

        group.add_option("--ignore-verify",
                         action="store_true",
                         default=False,
                         help=_("Bypass sha1sum verify step of source archive."))

        group.add_option("--ignore-action-errors",
                         action="store_true", default=False,
                         help=_("Bypass errors from ActionsAPI."))

        group.add_option("--ignore-safety",
                         action="store_true",
                         default=False,
                         help=_("Bypass safety switch."))

        group.add_option("--ignore-check",
                         action="store_true",
                         default=False,
                         help=_("Bypass testing step."))

        group.add_option("--create-static",
                         action="store_true",
                         default=False,
                         help=_("Create a static package with ar files."))

        group.add_option("-F", "--package-format",
                         action="store",
                         help=_("Create the binary package using the given "
                                "format. Use '-F help' to see a list of "
                                "supported formats."))

        group.add_option("--use-quilt",
                         action="store_true",
                         default=False,
                         help=_("Use quilt patch management system "
                                "instead of GNU patch."))

        group.add_option("--ignore-sandbox",
                         action="store_true",
                         default=False,
                         help=_("Do not constrain build process inside "
                                "the build folder."))

        self.parser.add_option_group(group)
        group = optparse.OptionGroup(self.parser, _("build steps"))

        group.add_option("--fetch",
                         dest="until",
                         action="store_const",
                         const="fetch",
                         help=_("Break build after fetching the source "
                                "archive."))

        group.add_option("--unpack",
                         dest="until",
                         action="store_const",
                         const="unpack",
                         help=_("Break build after unpacking the source "
                                "archive, checking sha1sum and applying "
                                "patches."))

        group.add_option("--setup",
                         dest="until",
                         action="store_const",
                         const="setup",
                         help=_("Break build after running configure step."))

        group.add_option("--build",
                         dest="until",
                         action="store_const",
                         const="build",
                         help=_("Break build after running compile step."))

        group.add_option("--check",
                         dest="until",
                         action="store_const",
                         const="check",
                         help=_("Break build after running check step."))

        group.add_option("--install",
                         dest="until",
                         action="store_const",
                         const="install",
                         help=_("Break build after running install step."))

        group.add_option("--package",
                         dest="until",
                         action="store_const",
                         const="package",
                         help=_("Create INARY package."))

        self.parser.add_option_group(group)

    def run(self):
        if not self.options.quiet:
            self.options.debug = True

        if self.options.package_format == "help":
            ctx.ui.info(_("Supported package formats:"))
            for format in inary.package.Package.formats:
                if format == inary.package.Package.default_format:
                    ctx.ui.info(_("  {} (default)").format(format))
                else:
                    ctx.ui.info("  {}".format(format))
            return

        self.init(False, False)

        if not ctx.get_option('output_dir'):
            ctx.config.options.output_dir = '.'

        for x in self.args or ["pspec.xml"]:
            if ctx.get_option('until'):
                inary.operations.build.build_until(x, ctx.get_option('until'))
            else:
                inary.operations.build.build(x)