Files
entropy/lib/entropy/db/skel.py
2011-10-17 14:27:58 +02:00

4631 lines
166 KiB
Python

# -*- coding: utf-8 -*-
"""
@author: Fabio Erculiani <lxnay@sabayon.org>
@contact: lxnay@sabayon.org
@copyright: Fabio Erculiani
@license: GPL-2
B{Entropy Framework repository database prototype classes module}.
"""
import os
import sys
import shutil
import warnings
import hashlib
import tempfile
from entropy.i18n import _
from entropy.exceptions import InvalidAtom
from entropy.const import etpConst, const_cmp, const_debug_write
from entropy.output import TextInterface, brown, bold, red, blue, purple, \
darkred
from entropy.cache import EntropyCacher
from entropy.core import EntropyPluginStore
from entropy.core.settings.base import SystemSettings
from entropy.exceptions import RepositoryPluginError
from entropy.spm.plugins.factory import get_default_instance as get_spm, \
get_default_class as get_spm_class
from entropy.db.exceptions import OperationalError
import entropy.dep
import entropy.tools
class EntropyRepositoryPlugin(object):
"""
This is the base class for implementing EntropyRepository plugin hooks.
You have to subclass this, implement not implemented methods and provide
it to EntropyRepository class as described below.
Every plugin hook function features this signature:
int something_hook(entropy_repository_instance)
Where entropy_repository_instance is the calling EntropyRepository instance.
Every method should return a return status code which, when nonzero causes
a RepositoryPluginError exception to be thrown.
Every method returns 0 in the base class implementation.
"""
def get_id(self):
"""
Return string identifier of myself.
@return: EntropyRepositoryPlugin identifier.
@rtype: string
"""
return str(self)
def get_metadata(self):
"""
Developers reimplementing EntropyRepositoryPlugin can provide metadata
along with every instance.
If you want to provide read-only metadata, this method should really
return a copy of the metadata object, otherwise, return its direct
reference.
Metadata format is a map-like object (dictionary, dict()).
By default this method does return an empty dict.
Make sure that your metadata dictionaries around don't have keys in
common, otherwise those will be randomly overwritten eachothers.
@return: plugin metadata
@rtype: dict
"""
return {}
def add_plugin_hook(self, entropy_repository_instance):
"""
Called during EntropyRepository plugin addition.
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
def remove_plugin_hook(self, entropy_repository_instance):
"""
Called during EntropyRepository plugin removal.
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
def commit_hook(self, entropy_repository_instance):
"""
Called during EntropyRepository data commit.
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
def close_repo_hook(self, entropy_repository_instance):
"""
Called during EntropyRepository instance shutdown (closeDB).
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
def add_package_hook(self, entropy_repository_instance, package_id,
package_data):
"""
Called after the addition of a package from EntropyRepository.
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@param package_id: Entropy repository package identifier
@type package_id: int
@param package_data: package metadata used for insertion
(see addPackage)
@type package_data: dict
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
def remove_package_hook(self, entropy_repository_instance, package_id,
from_add_package):
"""
Called after the removal of a package from EntropyRepository.
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@param package_id: Entropy repository package identifier
@type package_id: int
@param from_add_package: inform whether removePackage() is called inside
addPackage()
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
def clear_cache_hook(self, entropy_repository_instance):
"""
Called during EntropyRepository cache cleanup (clearCache).
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
def initialize_repo_hook(self, entropy_repository_instance):
"""
Called during EntropyRepository data initialization (not instance init).
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
def accept_license_hook(self, entropy_repository_instance):
"""
Called during EntropyRepository acceptLicense call.
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
def treeupdates_move_action_hook(self, entropy_repository_instance,
package_id):
"""
Called after EntropyRepository treeupdates move action execution for
given package_id in given EntropyRepository instance.
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@param package_id: Entropy repository package identifier
@type package_id: int
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
def treeupdates_slot_move_action_hook(self, entropy_repository_instance,
package_id):
"""
Called after EntropyRepository treeupdates slot move action
execution for given package_id in given EntropyRepository instance.
@param entropy_repository_instance: EntropyRepository instance
@type entropy_repository_instance: EntropyRepository
@param package_id: Entropy repository package identifier
@type package_id: int
@return: execution status code, return nonzero for errors, this will
raise a RepositoryPluginError exception.
@rtype: int
"""
return 0
class EntropyRepositoryPluginStore(EntropyPluginStore):
"""
EntropyRepository plugin interface. This is the EntropyRepository part
aimed to handle connected plugins.
"""
_PERMANENT_PLUGINS = {}
def __init__(self):
EntropyPluginStore.__init__(self)
permanent_plugs = EntropyRepositoryPluginStore.get_permanent_plugins()
for plug in permanent_plugs.values():
plug.add_plugin_hook(self)
def add_plugin(self, entropy_repository_plugin):
"""
Overloaded from EntropyPluginStore, adds support for hooks execution.
"""
inst = entropy_repository_plugin
if not isinstance(inst, EntropyRepositoryPlugin):
raise AttributeError("EntropyRepositoryPluginStore: " + \
"expected valid EntropyRepositoryPlugin instance")
EntropyPluginStore.add_plugin(self, inst.get_id(), inst)
inst.add_plugin_hook(self)
def remove_plugin(self, plugin_id):
"""
Overloaded from EntropyPluginStore, adds support for hooks execution.
"""
plugins = self.get_plugins()
plug_inst = plugins.get(plugin_id)
if plug_inst is not None:
plug_inst.remove_plugin_hook(self)
return EntropyPluginStore.remove_plugin(self, plugin_id)
@staticmethod
def add_permanent_plugin(entropy_repository_plugin):
"""
Add EntropyRepository permanent plugin. This plugin object will be
used across all the instantiated EntropyRepositoryPluginStore classes.
Each time a new instance is created, add_plugin_hook will be executed
for all the permanent plugins.
@param entropy_repository_plugin: EntropyRepositoryPlugin instance
@type entropy_repository_plugin: EntropyRepositoryPlugin instance
"""
inst = entropy_repository_plugin
if not isinstance(inst, EntropyRepositoryPlugin):
raise AttributeError("EntropyRepositoryPluginStore: " + \
"expected valid EntropyRepositoryPlugin instance")
EntropyRepositoryPluginStore._PERMANENT_PLUGINS[inst.get_id()] = inst
@staticmethod
def remove_permanent_plugin(plugin_id):
"""
Remove EntropyRepository permanent plugin. This plugin object will be
removed across all the EntropyRepository instances around.
Please note: due to the fact that there are no destructors around,
the "remove_plugin_hook" callback won't be executed when calling this
static method.
@param plugin_id: EntropyRepositoryPlugin identifier
@type plugin_id: string
@raise KeyError: in case of unavailable plugin identifier
"""
del EntropyRepositoryPluginStore._PERMANENT_PLUGINS[plugin_id]
@staticmethod
def get_permanent_plugins():
"""
Return EntropyRepositoryStore installed permanent plugins.
@return: copy of internal permanent plugins dict
@rtype: dict
"""
return EntropyRepositoryPluginStore._PERMANENT_PLUGINS.copy()
def get_plugins(self):
"""
Overloaded from EntropyPluginStore, adds support for permanent plugins.
"""
plugins = EntropyPluginStore.get_plugins(self)
plugins.update(EntropyRepositoryPluginStore.get_permanent_plugins())
return plugins
def get_plugins_metadata(self):
"""
Return EntropyRepositoryPluginStore registered plugins metadata.
@return: plugins metadata
@rtype: dict
"""
plugins = self.get_plugins()
meta = {}
for plugin_id in plugins:
meta.update(plugins[plugin_id].get_metadata())
return meta
def get_plugin_metadata(self, plugin_id, key):
"""
Return EntropyRepositoryPlugin metadata value referenced by "key".
@param plugin_id. EntropyRepositoryPlugin identifier
@type plugin_id: string
@param key: EntropyRepositoryPlugin metadatum identifier
@type key: string
@return: metadatum value
@rtype: any Python object
@raise KeyError: if provided key or plugin_id is not available
"""
plugins = self.get_plugins()
return plugins[plugin_id][key]
def set_plugin_metadata(self, plugin_id, key, value):
"""
Set EntropyRepositoryPlugin stored metadata.
@param plugin_id. EntropyRepositoryPlugin identifier
@type plugin_id: string
@param key: EntropyRepositoryPlugin metadatum identifier
@type key: string
@param value: value to set
@type value: any valid Python object
@raise KeyError: if plugin_id is not available
"""
plugins = self.get_plugins()
meta = plugins[plugin_id].get_metadata()
meta[key] = value
class EntropyRepositoryBase(TextInterface, EntropyRepositoryPluginStore):
"""
EntropyRepository interface base class.
This is an abstact class containing abstract methods that
subclasses need to reimplement.
Every Entropy repository object has to inherit this class.
"""
VIRTUAL_META_PACKAGE_CATEGORY = "virtual"
def __init__(self, readonly, xcache, temporary, name):
"""
EntropyRepositoryBase constructor.
@param readonly: readonly bit
@type readonly: bool
@param xcache: xcache bit (enable on-disk cache?)
@type xcache: bool
@param temporary: is this repo a temporary (non persistent) one?
@type temporary: bool
@param name: repository identifier (or name)
@type name: string
"""
TextInterface.__init__(self)
self._readonly = readonly
self._caching = xcache
self._temporary = temporary
self.name = name
# backward compatibility
self.reponame = name
self._settings = SystemSettings()
self._cacher = EntropyCacher()
self.__db_match_cache_key = EntropyCacher.CACHE_IDS['db_match']
EntropyRepositoryPluginStore.__init__(self)
def caching(self):
"""
Return whether caching is enabled in this repository.
@return: True, if caching is enabled
@rtype: bool
"""
return self._caching
def temporary(self):
"""
Return wheter the repository is temporary (in-memory, for example).
@return: True, if repository is temporary
@rtype: bool
"""
return self._temporary
def readonly(self):
"""
Return whether the repository is read-only.
This method shall always check real access
permissions.
@return: True, if repository is read-only
@rtype: bool
"""
return self._readonly
def repository_id(self):
"""
Return the repository identifier assigned to this instance.
@return: the repository identifier
@rtype: string
"""
return self.name
def close(self):
"""
Close repository storage communication and open disk files.
You can still use this instance, but closed files will be reopened.
Attention: call this method from your subclass, otherwise
EntropyRepositoryPlugins won't be notified of a repo close.
"""
if not self._readonly:
self.commit()
plugins = self.get_plugins()
for plugin_id in sorted(plugins):
plug_inst = plugins[plugin_id]
exec_rc = plug_inst.close_repo_hook(self)
if exec_rc:
raise RepositoryPluginError(
"[close_repo_hook] %s: status: %s" % (
plug_inst.get_id(), exec_rc,))
def closeDB(self):
"""
@deprecated, please see close()
"""
warnings.warn("EntropyRepositoryBase.closeDB() is now deprecated. " + \
"Please use close()")
return self.close()
def vacuum(self):
"""
Repository storage cleanup and optimization function.
"""
raise NotImplementedError()
def commit(self, force = False, no_plugins = False):
"""
Commit actual changes and make them permanently stored.
Attention: call this method from your subclass, otherwise
EntropyRepositoryPlugins won't be notified.
@keyword force: force commit, despite read-only bit being set
@type force: bool
@keyword no_plugins: disable EntropyRepository plugins execution
@type no_plugins: bool
"""
if no_plugins:
return
plugins = self.get_plugins()
for plugin_id in sorted(plugins):
plug_inst = plugins[plugin_id]
exec_rc = plug_inst.commit_hook(self)
if exec_rc:
raise RepositoryPluginError("[commit_hook] %s: status: %s" % (
plug_inst.get_id(), exec_rc,))
def rollback(self):
"""
Rollback last transaction, if it hasn't been already committed.
"""
raise NotImplementedError()
def commitChanges(self):
"""
@deprecated, please see commit()
"""
warnings.warn("EntropyRepositoryBase.commitChanges() " + \
"is now deprecated. Please use commit()")
return self.commit()
def initializeRepository(self):
"""
This method (re)initializes the repository, dropping all its content.
Attention: call this method from your subclass, otherwise
EntropyRepositoryPlugins won't be notified (AT THE END).
"""
plugins = self.get_plugins()
for plugin_id in sorted(plugins):
plug_inst = plugins[plugin_id]
exec_rc = plug_inst.initialize_repo_hook(self)
if exec_rc:
raise RepositoryPluginError(
"[initialize_repo_hook] %s: status: %s" % (
plug_inst.get_id(), exec_rc,))
def filterTreeUpdatesActions(self, actions):
"""
This method should be considered internal and not suited for general
audience. Given a raw package name/slot updates list, it returns
the action that should be really taken because not applied.
@param actions: list of raw treeupdates actions, for example:
['move x11-foo/bar app-foo/bar', 'slotmove x11-foo/bar 2 3']
@type actions: list
@return: list of raw treeupdates actions that should be really
worked out
@rtype: list
"""
new_actions = []
for action in actions:
if action in new_actions: # skip dupies
continue
doaction = action.split()
if doaction[0] == "slotmove":
# slot move
atom = doaction[1]
from_slot = doaction[2]
to_slot = doaction[3]
atom_key = entropy.dep.dep_getkey(atom)
category = atom_key.split("/")[0]
matches, sm_rc = self.atomMatch(atom, matchSlot = from_slot,
multiMatch = True, maskFilter = False)
if sm_rc == 1:
# nothing found in repo that matches atom
# this means that no packages can effectively
# reference to it
continue
found = False
# found atoms, check category
for package_id in matches:
myslot = self.retrieveSlot(package_id)
mycategory = self.retrieveCategory(package_id)
if mycategory == category:
if (myslot != to_slot) and \
(action not in new_actions):
new_actions.append(action)
found = True
break
if found:
continue
# if we get here it means found == False
# search into dependencies
dep_atoms = self.searchDependency(atom_key, like = True,
multi = True, strings = True)
dep_atoms = [x for x in dep_atoms if x.endswith(":"+from_slot) \
and entropy.dep.dep_getkey(x) == atom_key]
if dep_atoms:
new_actions.append(action)
elif doaction[0] == "move":
atom = doaction[1] # usually a key
atom_key = entropy.dep.dep_getkey(atom)
category = atom_key.split("/")[0]
matches, m_rc = self.atomMatch(atom, multiMatch = True,
maskFilter = False)
if m_rc == 1:
# nothing found in repo that matches atom
# this means that no packages can effectively
# reference to it
continue
found = False
for package_id in matches:
mycategory = self.retrieveCategory(package_id)
if (mycategory == category) and (action \
not in new_actions):
new_actions.append(action)
found = True
break
if found:
continue
# if we get here it means found == False
# search into dependencies
dep_atoms = self.searchDependency(atom_key, like = True,
multi = True, strings = True)
dep_atoms = [x for x in dep_atoms if \
entropy.dep.dep_getkey(x) == atom_key]
if dep_atoms:
new_actions.append(action)
return new_actions
def handlePackage(self, pkg_data, forcedRevision = -1,
formattedContent = False):
"""
Update or add a package to repository automatically handling
its scope and thus removal of previous versions if requested by
the given metadata.
pkg_data is a dict() containing all the information bound to
a package:
{
'signatures':
{
'sha256': 'zzz',
'sha1': 'zzz',
'sha512': 'zzz'
},
'slot': '0',
'datecreation': '1247681752.93',
'description': 'Standard (de)compression library',
'useflags': set(['kernel_linux']),
'config_protect_mask': 'string string', 'etpapi': 3,
'mirrorlinks': [],
'cxxflags': '-Os -march=x86-64 -pipe',
'injected': False,
'licensedata': {'ZLIB': u"lictext"},
'dependencies': {},
'chost': 'x86_64-pc-linux-gn',
'config_protect': 'string string',
'download': 'packages/amd64/4/sys-libs:zlib-1.2.3-r1.tbz2',
'conflicts': set([]),
'digest': 'fd54248ae060c287b1ec939de3e55332',
'size': '136302',
'category': 'sys-libs',
'license': 'ZLIB',
'sources': set(),
'name': 'zlib',
'versiontag': '',
'changelog': u"text",
'provide': set([]),
'trigger': 'text',
'counter': 22331,
'messages': [],
'branch': '4',
'content': {},
'content_safety': {},
'needed': [('libc.so.6', 2)],
'version': '1.2.3-r1',
'keywords': set(),
'cflags': '-Os -march=x86-64 -pipe',
'disksize': 932206, 'spm_phases': None,
'homepage': 'http://www.zlib.net/',
'systempackage': True,
'revision': 0
}
@param pkg_data: Entropy package metadata dict
@type pkg_data: dict
@keyword forcedRevision: force a specific package revision
@type forcedRevision: int
@keyword formattedContent: tells whether content metadata is already
formatted for insertion
@type formattedContent: bool
@return: package identifier
@rtype: int
"""
raise NotImplementedError()
def getPackagesToRemove(self, name, category, slot, injected):
"""
Return a list of packages that would be removed given name, category,
slot and injection status.
@param name: package name
@type name: string
@param category: package category
@type category: string
@param slot: package slot
@type slot: string
@param injected: injection status (packages marked as injected are
always considered not automatically removable)
@type injected: bool
@return: list (set) of removable packages (package_ids)
@rtype: set
"""
removelist = set()
if injected:
# read: if package has been injected, we'll skip
# the removal of packages in the same slot,
# usually used server side btw
return removelist
searchsimilar = self.searchNameCategory(name, category)
# support for expiration-based packages handling, also internally
# called Fat Scope.
filter_similar = False
srv_ss_plg = etpConst['system_settings_plugins_ids']['server_plugin']
srv_ss_fs_plg = \
etpConst['system_settings_plugins_ids']['server_plugin_fatscope']
srv_plug_settings = self._settings.get(srv_ss_plg)
if srv_plug_settings is not None:
if srv_plug_settings['server']['exp_based_scope']:
# in case support is enabled, return an empty set
filter_similar = True
if filter_similar:
# filter out packages in the same scope that are allowed to stay
idpkgs = self._settings[srv_ss_fs_plg]['repos'].get(
self.name)
if idpkgs:
if -1 in idpkgs:
searchsimilar = []
else:
searchsimilar = [x for x in searchsimilar if x[1] \
not in idpkgs]
for atom, package_id in searchsimilar:
# get the package slot
myslot = self.retrieveSlot(package_id)
# we merely ignore packages with
# negative counters, since they're the injected ones
if self.isInjected(package_id):
continue
if slot == myslot:
# remove!
removelist.add(package_id)
return removelist
def addPackage(self, pkg_data, revision = -1, package_id = None,
do_commit = True, formatted_content = False):
"""
Add package to this Entropy repository. The main difference between
handlePackage and this is that from here, no packages are going to be
removed, in any case.
For more information about pkg_data layout, please see
I{handlePackage()}.
Attention: call this method from your subclass (AT THE END), otherwise
EntropyRepositoryPlugins won't be notified.
@param pkg_data: Entropy package metadata
@type pkg_data: dict
@keyword revision: force a specific Entropy package revision
@type revision: int
@keyword package_id: add package to Entropy repository using the
provided package identifier, this is very dangerous and could
cause packages with the same identifier to be removed.
@type package_id: int
@keyword do_commit: if True, automatically commits the executed
transaction (could cause slowness)
@type do_commit: bool
@keyword formatted_content: if True, determines whether the content
metadata (usually the biggest part) in pkg_data is already
prepared for insertion
@type formatted_content: bool
@return: new package identifier
@rtype: int
"""
plugins = self.get_plugins()
for plugin_id in sorted(plugins):
plug_inst = plugins[plugin_id]
exec_rc = plug_inst.add_package_hook(self, package_id, pkg_data)
if exec_rc:
raise RepositoryPluginError(
"[add_package_hook] %s: status: %s" % (
plug_inst.get_id(), exec_rc,))
def removePackage(self, package_id, do_cleanup = True, do_commit = True,
from_add_package = False):
"""
Remove package from this Entropy repository using it's identifier
(package_id).
Attention: call this method from your subclass, otherwise
EntropyRepositoryPlugins won't be notified.
@param package_id: Entropy repository package indentifier
@type package_id: int
@keyword do_cleanup: if True, executes repository metadata cleanup
at the end
@type do_cleanup: bool
@keyword do_commit: if True, commits the transaction (could cause
slowness)
@type do_commit: bool
@keyword from_add_package: inform function that it's being called from
inside addPackage().
@type from_add_package: bool
"""
plugins = self.get_plugins()
for plugin_id in sorted(plugins):
plug_inst = plugins[plugin_id]
exec_rc = plug_inst.remove_package_hook(self, package_id,
from_add_package)
if exec_rc:
raise RepositoryPluginError(
"[remove_package_hook] %s: status: %s" % (
plug_inst.get_id(), exec_rc,))
def setInjected(self, package_id, do_commit = True):
"""
Mark package as injected, injection is usually set for packages
manually added to repository. Injected packages are not removed
automatically even when featuring conflicting scope with other
that are being added. If a package is injected, it means that
maintainers have to handle it manually.
@param package_id: package indentifier
@type package_id: int
@keyword do_commit: determine whether executing commit or not
@type do_commit: bool
"""
raise NotImplementedError()
def setCreationDate(self, package_id, date):
"""
Update the creation date for package. Creation date is stored in
string based unix time format.
@param package_id: package indentifier
@type package_id: int
@param date: unix time in string form
@type date: string
"""
raise NotImplementedError()
def setDigest(self, package_id, digest):
"""
Set package file md5sum for package. This information is used
by entropy.client when downloading packages.
@param package_id: package indentifier
@type package_id: int
@param digest: md5 hash for package file
@type digest: string
"""
raise NotImplementedError()
def setSignatures(self, package_id, sha1, sha256, sha512, gpg = None):
"""
Set package file extra hashes (sha1, sha256, sha512) for package.
@param package_id: package indentifier
@type package_id: int
@param sha1: SHA1 hash for package file
@type sha1: string
@param sha256: SHA256 hash for package file
@type sha256: string
@param sha512: SHA512 hash for package file
@type sha512: string
@keyword gpg: GPG signature file content
@type gpg: string
"""
raise NotImplementedError()
def setDownloadURL(self, package_id, url):
"""
Set download URL prefix for package.
@param package_id: package indentifier
@type package_id: int
@param url: URL prefix to set
@type url: string
"""
raise NotImplementedError()
def setName(self, package_id, name):
"""
Set name for package.
@param package_id: package indentifier
@type package_id: int
@param name: package name
@type name: string
"""
raise NotImplementedError()
def setAtom(self, package_id, atom):
"""
Set atom string for package. "Atom" is the full, unique name of
a package.
@param package_id: package indentifier
@type package_id: int
@param atom: atom string
@type atom: string
"""
raise NotImplementedError()
def setSlot(self, package_id, slot):
"""
Set slot string for package. Please refer to Portage SLOT documentation
for more info.
@param package_id: package indentifier
@type package_id: int
@param slot: slot string
@type slot: string
"""
raise NotImplementedError()
def setDependency(self, iddependency, dependency):
"""
Set dependency string for iddependency (dependency identifier).
@param iddependency: dependency string identifier
@type iddependency: int
@param dependency: dependency string
@type dependency: string
"""
raise NotImplementedError()
def setCategory(self, package_id, category):
"""
Set category name for package.
@param package_id: package indentifier
@type package_id: int
@param category: category to set
@type category: string
"""
raise NotImplementedError()
def setCategoryDescription(self, category, description_data):
"""
Set description for given category name.
@param category: category name
@type category: string
@param description_data: category description for several locales.
{'en': "This is blah", 'it': "Questo e' blah", ... }
@type description_data: dict
"""
raise NotImplementedError()
def setRevision(self, package_id, revision):
"""
Set Entropy revision for package.
@param package_id: package indentifier
@type package_id: int
@param revision: new revision
@type revision: int
"""
raise NotImplementedError()
def setContentSafety(self, package_id, content_safety):
"""
Set (overwriting previous entries) new content safety metadata.
@param package_id: package indentifier
@type package_id: int
@param content_safety: dictionary with the same data structure of the
one returned by retrieveContentSafety()
@type content_safety: dict
"""
raise NotImplementedError()
def removeDependencies(self, package_id):
"""
Remove all the dependencies of package.
@param package_id: package indentifier
@type package_id: int
"""
raise NotImplementedError()
def insertDependencies(self, package_id, depdata):
"""
Insert dependencies for package. "depdata" is a dict() with dependency
strings as keys and dependency type as values.
@param package_id: package indentifier
@type package_id: int
@param depdata: dependency dictionary
{'app-foo/foo': dep_type_integer, ...}
@type depdata: dict
"""
raise NotImplementedError()
def insertContent(self, package_id, content, already_formatted = False):
"""
Insert content metadata for package. "content" can either be a dict()
or a list of triples (tuples of length 3, (package_id, path, type,)).
This method expects Unicode strings. Passing 8-bit raw strings will
cause unpredictable results.
@param package_id: package indentifier
@type package_id: int
@param content: content metadata to insert.
{'/path/to/foo': 'obj(content type)',}
or
[(package_id, path, type,) ...]
@type content: dict, list
@keyword already_formatted: if True, "content" is expected to be
already formatted for insertion, this means that "content" must be
a list of tuples of length 3.
@type already_formatted: bool
"""
raise NotImplementedError()
def insertAutomergefiles(self, package_id, automerge_data):
"""
Insert configuration files automerge information for package.
"automerge_data" contains configuration files paths and their belonging
md5 hash.
This features allows entropy.client to "auto-merge" or "auto-remove"
configuration files never touched by user.
This method expects Unicode strings. Passing 8-bit raw strings will
cause unpredictable results.
@param package_id: package indentifier
@type package_id: int
@param automerge_data: list of tuples of length 2.
[('/path/to/conf/file', 'md5_checksum_string',) ... ]
@type automerge_data: list
"""
raise NotImplementedError()
def insertBranchMigration(self, repository, from_branch, to_branch,
post_migration_md5sum, post_upgrade_md5sum):
"""
Insert Entropy Client "branch migration" scripts hash metadata.
When upgrading from a branch to another, it can happen that repositories
ship with scripts aiming to ease the upgrade.
This method stores in the repository information on such scripts.
@param repository: repository identifier
@type repository: string
@param from_branch: original branch
@type from_branch: string
@param to_branch: destination branch
@type to_branch: string
@param post_migration_md5sum: md5 hash related to "post-migration"
branch script file
@type post_migration_md5sum: string
@param post_upgrade_md5sum: md5 hash related to "post-upgrade on new
branch" script file
@type post_upgrade_md5sum: string
"""
raise NotImplementedError()
def setBranchMigrationPostUpgradeMd5sum(self, repository, from_branch,
to_branch, post_upgrade_md5sum):
"""
Update "post-upgrade on new branch" script file md5 hash.
When upgrading from a branch to another, it can happen that repositories
ship with scripts aiming to ease the upgrade.
This method stores in the repository information on such scripts.
@param repository: repository identifier
@type repository: string
@param from_branch: original branch
@type from_branch: string
@param to_branch: destination branch
@type to_branch: string
@param post_upgrade_md5sum: md5 hash related to "post-upgrade on new
branch" script file
@type post_upgrade_md5sum: string
"""
raise NotImplementedError()
def insertSpmUid(self, package_id, spm_package_uid):
"""
Insert Source Package Manager unique package identifier and bind it
to Entropy package identifier given (package_id). This method is used
by Entropy Client and differs from "_bindSpmPackageUid" because
any other colliding package_id<->uid binding is overwritten by design.
@param package_id: package indentifier
@type package_id: int
@param spm_package_uid: Source package Manager unique package identifier
@type spm_package_uid: int
"""
raise NotImplementedError()
def setTrashedUid(self, spm_package_uid):
"""
Mark given Source Package Manager unique package identifier as
"trashed". This is a trick to allow Entropy Server to support
multiple repositories and parallel handling of them without
make it messing with removed packages from the underlying system.
@param spm_package_uid: Source package Manager unique package identifier
@type spm_package_uid: int
"""
raise NotImplementedError()
def setSpmUid(self, package_id, spm_package_uid, branch = None):
"""
Update Source Package Manager unique package identifier for given
Entropy package identifier (package_id).
This method *only* updates a currently available binding setting a new
"spm_package_uid"
@param package_id: package indentifier
@type package_id: int
@param spm_package_uid: Source package Manager unique package identifier
@type spm_package_uid: int
@keyword branch: current Entropy repository branch
@type branch: string
"""
raise NotImplementedError()
def contentDiff(self, package_id, dbconn, dbconn_package_id):
"""
Return content metadata difference between two packages.
@param package_id: package indentifier available in this repository
@type package_id: int
@param dbconn: other repository class instance
@type dbconn: EntropyRepository
@param dbconn_package_id: package identifier available in other
repository
@type dbconn_package_id: int
@return: content difference
@rtype: frozenset
@raise AttributeError: when self instance and dbconn are the same
"""
raise NotImplementedError()
def clean(self):
"""
Run repository metadata cleanup over unused references.
"""
raise NotImplementedError()
def getDependency(self, iddependency):
"""
Return dependency string for given dependency identifier.
@param iddependency: dependency identifier
@type iddependency: int
@return: dependency string
@rtype: string or None
"""
raise NotImplementedError()
def getFakeSpmUid(self):
"""
Obtain auto-generated available negative Source Package Manager
package identifier.
@return: new negative spm uid
@rtype: int
"""
raise NotImplementedError()
def getApi(self):
"""
Get Entropy repository API.
@return: Entropy repository API
@rtype: int
"""
raise NotImplementedError()
def getPackageIds(self, atom):
"""
Obtain repository package identifiers from atom string.
@param atom: package atom
@type atom: string
@return: list of matching package_ids found
@rtype: frozenset
"""
raise NotImplementedError()
def getPackageIdFromDownload(self, download_relative_path,
endswith = False):
"""
Obtain repository package identifier from its relative download path
string.
@param download_relative_path: relative download path string returned
by "retrieveDownloadURL" method
@type download_relative_path: string
@keyword endswith: search for package_id which download metadata ends
with the one provided by download_relative_path
@type endswith: bool
@return: package_id in repository or -1 if not found
@rtype: int
"""
raise NotImplementedError()
def getVersioningData(self, package_id):
"""
Get package version information for provided package identifier.
@param package_id: package indentifier
@type package_id: int
@return: tuple of length 3 composed by (version, tag, revision,)
belonging to package_id
@rtype: tuple
"""
raise NotImplementedError()
def getStrictData(self, package_id):
"""
Get a restricted (optimized) set of package metadata for provided
package identifier.
@param package_id: package indentifier
@type package_id: int
@return: tuple of length 6 composed by
(package key, slot, version, tag, revision, atom)
belonging to package_id
@rtype: tuple
"""
raise NotImplementedError()
def getStrictScopeData(self, package_id):
"""
Get a restricted (optimized) set of package metadata for provided
identifier that can be used to determine the scope of package.
@param package_id: package indentifier
@type package_id: int
@return: tuple of length 3 composed by (atom, slot, revision,)
belonging to package_id
@rtype: tuple
"""
raise NotImplementedError()
def getScopeData(self, package_id):
"""
Get a set of package metadata for provided identifier that can be
used to determine the scope of package.
@param package_id: package indentifier
@type package_id: int
@return: tuple of length 9 composed by
(atom, category name, name, version,
slot, tag, revision, branch, api,)
belonging to package_id
@rtype: tuple
"""
raise NotImplementedError()
def getBaseData(self, package_id):
"""
Get a set of basic package metadata for provided package identifier.
@param package_id: package indentifier
@type package_id: int
@return: tuple of length 19 composed by
(atom, name, version, tag, description, category name, CHOST,
CFLAGS, CXXFLAGS, homepage, license, branch, download path, digest,
slot, api, creation date, package size, revision,)
belonging to package_id
@rtype: tuple
"""
raise NotImplementedError()
def getTriggerData(self, package_id, content = True):
"""
Get a set of basic package metadata for provided package identifier.
This method is optimized to work with Entropy Client installation
triggers returning only what is strictly needed.
@param package_id: package indentifier
@type package_id: int
@keyword content: if True, grabs the "content" metadata too, othewise
such dict key value will be shown as empty set().
@type content: bool
@return: dictionary containing package metadata
data = {
'atom': atom,
'category': category,
'name': name,
'version': version,
'slot': slot,
'versiontag': versiontag,
'revision': revision,
'branch': branch,
'chost': chost,
'cflags': cflags,
'cxxflags': cxxflags,
'etpapi': etpapi,
'trigger': self.retrieveTrigger(package_id),
'content': pkg_content,
'spm_phases': self.retrieveSpmPhases(package_id),
}
@rtype: dict or None
"""
scope_data = self.getScopeData(package_id)
if scope_data is None:
return
atom, category, name, \
version, slot, versiontag, \
revision, branch, etpapi = scope_data
chost, cflags, cxxflags = self.retrieveCompileFlags(package_id)
pkg_content = set()
if content:
pkg_content = self.retrieveContent(package_id)
data = {
'atom': atom,
'category': category,
'name': name,
'version': version,
'slot': slot,
'versiontag': versiontag,
'revision': revision,
'branch': branch,
'chost': chost,
'cflags': cflags,
'cxxflags': cxxflags,
'etpapi': etpapi,
'trigger': self.retrieveTrigger(package_id),
'content': pkg_content,
'spm_phases': self.retrieveSpmPhases(package_id),
}
return data
def getPackageData(self, package_id, get_content = True,
content_insert_formatted = False, get_changelog = True):
"""
Reconstruct all the package metadata belonging to provided package
identifier into a dict object.
@param package_id: package indentifier
@type package_id: int
@keyword get_content:
@type get_content: bool
@keyword content_insert_formatted:
@type content_insert_formatted: bool
@keyword get_changelog: return ChangeLog text metadatum or None
@type get_changelog: bool
@return: package metadata in dict() form
>>> data = {
'atom': atom,
'name': name,
'version': version,
'versiontag':versiontag,
'description': description,
'category': category,
'chost': chost,
'cflags': cflags,
'cxxflags': cxxflags,
'homepage': homepage,
'license': mylicense,
'branch': branch,
'download': download,
'digest': digest,
'slot': slot,
'etpapi': etpapi,
'datecreation': datecreation,
'size': size,
'revision': revision,
'counter': self.retrieveSpmUid(package_id),
'messages': [], deprecated
'trigger': self.retrieveTrigger(package_id),
'disksize': self.retrieveOnDiskSize(package_id),
'changelog': self.retrieveChangelog(package_id),
'injected': self.isInjected(package_id),
'systempackage': self.isSystemPackage(package_id),
'config_protect': self.retrieveProtect(package_id),
'config_protect_mask': self.retrieveProtectMask(package_id),
'useflags': self.retrieveUseflags(package_id),
'keywords': self.retrieveKeywords(package_id),
'sources': sources,
'needed': self.retrieveNeeded(package_id, extended = True),
'provided_libs': self.retrieveProvidedLibraries(package_id),
'provide': provide (the old provide metadata version)
'provide_extended': self.retrieveProvide(package_id),
'conflicts': self.retrieveConflicts(package_id),
'licensedata': self.retrieveLicenseData(package_id),
'content': content,
'content_safety': {},
'dependencies': dict((x, y,) for x, y in \
self.retrieveDependencies(package_id, extended = True)),
'mirrorlinks': [[x,self.retrieveMirrorData(x)] for x in mirrornames],
'signatures': signatures,
'spm_phases': self.retrieveSpmPhases(package_id),
'spm_repository': self.retrieveSpmRepository(package_id),
'desktop_mime': [],
'provided_mime': [],
'original_repository': self.getInstalledPackageRepository(package_id),
'extra_download': self.retrieveExtraDownload(package_id),
}
@rtype: dict
"""
data = {}
try:
atom, name, version, versiontag, \
description, category, chost, \
cflags, cxxflags, homepage, \
mylicense, branch, download, \
digest, slot, etpapi, \
datecreation, size, revision = self.getBaseData(package_id)
except TypeError:
return None
content = {}
if get_content:
content = self.retrieveContent(
package_id, extended = True,
formatted = True, insert_formatted = content_insert_formatted
)
sources = self.retrieveSources(package_id)
mirrornames = set()
for x in sources:
if x.startswith("mirror://"):
mirrornames.add(x.split("/")[2])
sha1, sha256, sha512, gpg = self.retrieveSignatures(package_id)
signatures = {
'sha1': sha1,
'sha256': sha256,
'sha512': sha512,
'gpg': gpg,
}
provide_extended = self.retrieveProvide(package_id)
# TODO: remove this before 31-12-2011
old_provide = set()
for x in provide_extended:
if isinstance(x, tuple):
old_provide.add(x[0])
else:
old_provide.add(x)
changelog = None
if get_changelog:
changelog = self.retrieveChangelog(package_id)
data = {
'atom': atom,
'name': name,
'version': version,
'versiontag': versiontag,
'description': description,
'category': category,
'chost': chost,
'cflags': cflags,
'cxxflags': cxxflags,
'homepage': homepage,
'license': mylicense,
'branch': branch,
'download': download,
'digest': digest,
'slot': slot,
'etpapi': etpapi,
'datecreation': datecreation,
'size': size,
'revision': revision,
# risky to add to the sql above, still
'counter': self.retrieveSpmUid(package_id),
'messages': [],
# FIXME: backward compatibility, drop after 2011
'eclasses': [],
'trigger': self.retrieveTrigger(package_id),
'disksize': self.retrieveOnDiskSize(package_id),
'changelog': changelog,
'injected': self.isInjected(package_id),
'systempackage': self.isSystemPackage(package_id),
'config_protect': self.retrieveProtect(package_id),
'config_protect_mask': self.retrieveProtectMask(package_id),
'useflags': self.retrieveUseflags(package_id),
'keywords': self.retrieveKeywords(package_id),
'sources': sources,
'needed': self.retrieveNeeded(package_id, extended = True),
'provided_libs': self.retrieveProvidedLibraries(package_id),
'provide': old_provide,
'provide_extended': provide_extended,
'conflicts': self.retrieveConflicts(package_id),
'licensedata': self.retrieveLicenseData(package_id),
'content': content,
'content_safety': self.retrieveContentSafety(package_id),
'dependencies': dict((x, y,) for x, y in \
self.retrieveDependencies(package_id, extended = True,
resolve_conditional_deps = False)),
'mirrorlinks': [[x, self.retrieveMirrorData(x)] for x in mirrornames],
'signatures': signatures,
'spm_phases': self.retrieveSpmPhases(package_id),
'spm_repository': self.retrieveSpmRepository(package_id),
'desktop_mime': self.retrieveDesktopMime(package_id),
'provided_mime': self.retrieveProvidedMime(package_id),
'original_repository': self.getInstalledPackageRepository(package_id),
'extra_download': self.retrieveExtraDownload(package_id),
}
return data
def clearCache(self):
"""
Clear repository cache.
Attention: call this method from your subclass, otherwise
EntropyRepositoryPlugins won't be notified.
"""
plugins = self.get_plugins()
for plugin_id in sorted(plugins):
plug_inst = plugins[plugin_id]
exec_rc = plug_inst.clear_cache_hook(self)
if exec_rc:
raise RepositoryPluginError(
"[clear_cache_hook] %s: status: %s" % (
plug_inst.get_id(), exec_rc,))
def retrieveRepositoryUpdatesDigest(self, repository):
"""
This method should be considered internal and not suited for general
audience. Return digest (md5 hash) bound to repository package
names/slots updates.
@param repository: repository identifier
@type repository: string
@return: digest string
@rtype: string
"""
raise NotImplementedError()
def _runConfigurationFilesUpdate(self, actions, files,
protect_overwrite = True):
"""
Routine that takes all the executed actions and updates configuration
files.
"""
spm_class = get_spm_class()
updated_files = set()
actions_map = {}
for action in actions:
command = action.split()
dep_key = entropy.dep.dep_getkey(command[1])
obj = actions_map.setdefault(dep_key, [])
obj.append(tuple(command))
def _workout_line(line):
if not line.strip():
return line
if line.lstrip().startswith("#"):
return line
split_line = line.split()
if not split_line:
return line
pkg_dep = split_line[0]
pkg_key = entropy.dep.dep_getkey(pkg_dep)
pkg_commands = actions_map.get(pkg_key)
if pkg_commands is None:
return line
for command in pkg_commands:
if command[0] == "move":
dep_from, key_to = command[1:]
dep_from_key = entropy.dep.dep_getkey(dep_from)
# NOTE: dep matching not supported, only using key
if dep_from_key == pkg_key:
# found, replace package name
split_line[0] = pkg_dep.replace(dep_from_key, key_to)
new_line = " ".join(split_line) + "\n"
const_debug_write(__name__,
"_runConfigurationFilesUpdate: replacing: " + \
"'%s' => '%s'" % (line, new_line,))
line = new_line
# keep going, since updates are incremental
# NOTE: slotmove not supported
return line
for file_path in files:
if not (os.path.isfile(file_path) and \
os.access(file_path, os.W_OK)):
continue
tmp_fd, tmp_path = None, None
try:
with open(file_path, "r") as source_f:
tmp_fd, tmp_path = tempfile.mkstemp(
prefix="entropy.db._runConfigurationFilesUpdate",
dir=os.path.dirname(file_path))
with os.fdopen(tmp_fd, "w") as dest_f:
line = source_f.readline()
while line:
dest_f.write(_workout_line(line))
line = source_f.readline()
if protect_overwrite:
new_file_path, prot_status = \
spm_class.allocate_protected_file(
tmp_path, file_path)
if prot_status:
# it has been replaced
os.rename(tmp_path, new_file_path)
updated_files.add(new_file_path)
else:
os.remove(tmp_path)
else:
os.rename(tmp_path, file_path)
tmp_path = None
tmp_fd = None
except (OSError, IOError,) as err:
const_debug_write(__name__, "error: %s" % (err,))
if tmp_path is not None:
try:
os.remove(tmp_path)
except (OSError, IOError):
pass
if tmp_fd is not None:
try:
os.close(tmp_fd)
except (OSError, IOError):
pass
continue
return updated_files
def runTreeUpdatesActions(self, actions):
"""
Method not suited for general purpose usage.
Executes package name/slot update actions passed.
@param actions: list of raw treeupdates actions, for example:
['move x11-foo/bar app-foo/bar', 'slotmove x11-foo/bar 2 3']
@type actions: list
@return: list (set) of packages that should be repackaged
@rtype: set
"""
mytxt = "%s: %s, %s." % (
bold(_("SPM")),
blue(_("Running packages metadata update")),
red(_("it could take a while")),
)
self.output(
mytxt,
importance = 1,
level = "warning",
header = darkred(" * ")
)
try:
spm = get_spm(self)
spm.packages_repositories_metadata_update()
except Exception:
entropy.tools.print_traceback()
spm_moves = set()
quickpkg_atoms = set()
executed_actions = []
for action in actions:
command = action.split()
mytxt = "%s: %s: %s." % (
bold(_("ENTROPY")),
red(_("action")),
blue(action),
)
self.output(
mytxt,
importance = 1,
level = "warning",
header = darkred(" * ")
)
if command[0] == "move":
spm_moves.add(action)
move_actions = self._runTreeUpdatesMoveAction(command[1:],
quickpkg_atoms)
if move_actions:
executed_actions.append(action)
quickpkg_atoms |= move_actions
elif command[0] == "slotmove":
slotmove_actions = self._runTreeUpdatesSlotmoveAction(command[1:],
quickpkg_atoms)
if slotmove_actions:
executed_actions.append(action)
quickpkg_atoms |= slotmove_actions
mytxt = "%s: %s." % (
bold(_("ENTROPY")),
blue(_("package move actions complete")),
)
self.output(
mytxt,
importance = 1,
level = "info",
header = purple(" @@ ")
)
if spm_moves:
try:
self._doTreeupdatesSpmCleanup(spm_moves)
except Exception as e:
mytxt = "%s: %s: %s, %s." % (
bold(_("WARNING")),
red(_("Cannot run SPM cleanup, error")),
Exception,
e,
)
entropy.tools.print_traceback()
mytxt = "%s: %s." % (
bold(_("ENTROPY")),
blue(_("package moves completed successfully")),
)
self.output(
mytxt,
importance = 1,
level = "info",
header = brown(" @@ ")
)
if executed_actions:
# something actually happened, update configuration files
files = self._settings.get_updatable_configuration_files(
self.repository_id())
self._runConfigurationFilesUpdate(executed_actions, files)
# discard cache
self.clearCache()
return quickpkg_atoms
def _runTreeUpdatesMoveAction(self, move_command, quickpkg_queue):
"""
Method not suited for general purpose usage.
Executes package name move action passed.
No need to override.
-- move action:
1) move package key to the new name: category + name + atom
2) update all the dependencies in dependenciesreference to the new key
3) run fixpackages which will update /var/db/pkg files
4) automatically run generate_package() to build the new binary and
tainted binaries owning tainted iddependency and taint database
@param move_command: raw treeupdates move action, for example:
'move x11-foo/bar app-foo/bar'
@type move_command: string
@param quickpkg_queue: current package regeneration queue
@type quickpkg_queue: list
@return: updated package regeneration queue
@rtype: list
"""
dep_from = move_command[0]
key_from = entropy.dep.dep_getkey(dep_from)
key_to = entropy.dep.dep_getkey(move_command[1])
cat_to, name_to = key_to.split("/", 1)
matches = self.atomMatch(dep_from, multiMatch = True,
maskFilter = False)
iddependencies = set()
slot_pfx = etpConst['entropyslotprefix']
matched_package_ids = matches[0]
for package_id in matched_package_ids:
slot = self.retrieveSlot(package_id)
old_atom = self.retrieveAtom(package_id)
new_atom = old_atom.replace(key_from, key_to)
### UPDATE DATABASE
# update category
self.setCategory(package_id, cat_to)
# update name
self.setName(package_id, name_to)
# update atom
self.setAtom(package_id, new_atom)
# look for packages we need to quickpkg again
quickpkg_queue.add(key_to + slot_pfx + slot)
plugins = self.get_plugins()
for plugin_id in sorted(plugins):
plug_inst = plugins[plugin_id]
exec_rc = plug_inst.treeupdates_move_action_hook(self,
package_id)
if exec_rc:
raise RepositoryPluginError(
"[treeupdates_move_action_hook] %s: status: %s" % (
plug_inst.get_id(), exec_rc,))
iddeps = self.searchDependency(key_from, like = True, multi = True)
for iddep in iddeps:
mydep = self.getDependency(iddep)
# replace with new key and test
mydep = mydep.replace(key_from, key_to)
pkg_ids, pkg_rc = self.atomMatch(mydep, multiMatch = True,
maskFilter = False)
pointing_to_me = False
for pkg_id in pkg_ids:
if pkg_id not in matched_package_ids:
# not my business
continue
# is this really pointing to me?
mydep_key, _slot = self.retrieveKeySlot(pkg_id)
if mydep_key != key_to:
# not me!
continue
# yes, it's pointing to me
pointing_to_me = True
break
if not pointing_to_me:
# meh !
continue
# now update
# dependstable on server is always re-generated
self.setDependency(iddep, mydep)
# we have to repackage also package owning this iddep
iddependencies |= self.searchPackageIdFromDependencyId(iddep)
self.commit()
quickpkg_queue = list(quickpkg_queue)
for x in range(len(quickpkg_queue)):
myatom = quickpkg_queue[x]
myatom = myatom.replace(key_from, key_to)
quickpkg_queue[x] = myatom
quickpkg_queue = set(quickpkg_queue)
for package_id_owner in iddependencies:
myatom = self.retrieveAtom(package_id_owner)
if myatom is None:
# reverse deps table out of sync
continue
myatom = myatom.replace(key_from, key_to)
quickpkg_queue.add(myatom)
return quickpkg_queue
def _runTreeUpdatesSlotmoveAction(self, slotmove_command, quickpkg_queue):
"""
Method not suited for general purpose usage.
Executes package slot move action passed.
No need to override.
-- slotmove action:
1) move package slot
2) update all the dependencies in dependenciesreference owning
same matched atom + slot
3) run fixpackages which will update /var/db/pkg files
4) automatically run generate_package() to build the new
binary and tainted binaries owning tainted iddependency
and taint database
@param slotmove_command: raw treeupdates slot move action, for example:
'slotmove x11-foo/bar 2 3'
@type slotmove_command: string
@param quickpkg_queue: current package regeneration queue
@type quickpkg_queue: list
@return: updated package regeneration queue
@rtype: list
"""
atom = slotmove_command[0]
atomkey = entropy.dep.dep_getkey(atom)
slot_from = slotmove_command[1]
slot_to = slotmove_command[2]
matches = self.atomMatch(atom, multiMatch = True, maskFilter = False)
iddependencies = set()
slot_pfx = etpConst['entropyslotprefix']
matched_package_ids = matches[0]
for package_id in matched_package_ids:
# only if we've found VALID matches !
iddeps = self.searchDependency(atomkey, like = True, multi = True)
for iddep in iddeps:
# update string
mydep = self.getDependency(iddep)
if mydep.find(slot_pfx + slot_from) == -1:
# doesn't contain any trace of slot string, skipping
continue
pkg_ids, pkg_rc = self.atomMatch(mydep, multiMatch = True,
maskFilter = False)
pointing_to_me = False
for pkg_id in pkg_ids:
if pkg_id not in matched_package_ids:
# not my business
continue
# is this really pointing to me?
mydep_key, mydep_slot = self.retrieveKeySlot(pkg_id)
if mydep_key != atomkey:
# not me!
continue
if mydep_slot != slot_from:
# not me!
continue
# yes, it's pointing to me
pointing_to_me = True
break
if not pointing_to_me:
# meh !
continue
mydep = mydep.replace(slot_pfx + slot_from, slot_pfx + slot_to)
# now update
# dependstable on server is always re-generated
self.setDependency(iddep, mydep)
# we have to repackage also package owning this iddep
iddependencies |= self.searchPackageIdFromDependencyId(iddep)
### UPDATE DATABASE
# update slot, do it here to avoid messing up with package match
# code up here
self.setSlot(package_id, slot_to)
# look for packages we need to quickpkg again
# NOTE: quickpkg_queue is simply ignored if this is a client side
# repository
quickpkg_queue.add(atom + slot_pfx + slot_to)
plugins = self.get_plugins()
for plugin_id in sorted(plugins):
plug_inst = plugins[plugin_id]
exec_rc = plug_inst.treeupdates_slot_move_action_hook(self,
package_id)
if exec_rc:
raise RepositoryPluginError(
"[treeupdates_slot_move_action_hook] %s: status: %s" % (
plug_inst.get_id(), exec_rc,))
self.commit()
for package_id_owner in iddependencies:
myatom = self.retrieveAtom(package_id_owner)
if myatom is None:
# reverse deps table out of sync
continue
quickpkg_queue.add(myatom)
return quickpkg_queue
def _doTreeupdatesSpmCleanup(self, spm_moves):
"""
Erase dead Source Package Manager db entries.
@todo: make more Portage independent (create proper entropy.spm
methods for dealing with this)
@param spm_moves: list of raw package name/slot update actions.
@type spm_moves: list
"""
# now erase Spm entries if necessary
for action in spm_moves:
command = action.split()
if len(command) < 2:
continue
key = command[1]
category, name = key.split("/", 1)
dep_key = entropy.dep.dep_getkey(key)
try:
spm = get_spm(self)
except Exception:
entropy.tools.print_traceback()
continue
script_path = spm.get_installed_package_build_script_path(dep_key)
pkg_path = os.path.dirname(os.path.dirname(script_path))
if not os.path.isdir(pkg_path):
# no dir, no party!
continue
mydirs = [os.path.join(pkg_path, x) for x in \
os.listdir(pkg_path) if \
entropy.dep.dep_getkey(os.path.join(category, x)) \
== dep_key]
mydirs = [x for x in mydirs if os.path.isdir(x)]
# now move these dirs
for mydir in mydirs:
to_path = os.path.join(etpConst['packagestmpdir'],
os.path.basename(mydir))
mytxt = "%s: %s '%s' %s '%s'" % (
bold(_("SPM")),
red(_("Moving old entry")),
blue(mydir),
red(_("to")),
blue(to_path),
)
self.output(
mytxt,
importance = 1,
level = "warning",
header = darkred(" * ")
)
if os.path.isdir(to_path):
shutil.rmtree(to_path, True)
try:
os.rmdir(to_path)
except OSError:
pass
shutil.move(mydir, to_path)
def listAllTreeUpdatesActions(self, no_ids_repos = False):
"""
This method should be considered internal and not suited for general
audience.
List all the available "treeupdates" (package names/slots changes
directives) actions.
@keyword no_ids_repos: if True, it will just return a tuple of 3-length
tuples containing ((command, branch, unix_time,), ...)
@type no_ids_repos: bool
@return: tuple of tuples
@rtype: tuple
"""
raise NotImplementedError()
def retrieveTreeUpdatesActions(self, repository):
"""
This method should be considered internal and not suited for general
audience.
Return all the available "treeupdates (package names/slots changes
directives) actions for provided repository.
@param repository: repository identifier
@type repository: string
@return: tuple of raw-string commands to run
@rtype: tuple
"""
raise NotImplementedError()
def bumpTreeUpdatesActions(self, updates):
# mainly used to restore a previous table,
# used by reagent in --initialize
"""
This method should be considered internal and not suited for general
audience.
This method rewrites "treeupdates" metadata in repository.
@param updates: new treeupdates metadata
@type updates: list
"""
raise NotImplementedError()
def removeTreeUpdatesActions(self, repository):
"""
This method should be considered internal and not suited for general
audience.
This method removes "treeupdates" metadata in repository.
@param repository: remove treeupdates metadata for provided repository
@type repository: string
"""
raise NotImplementedError()
def insertTreeUpdatesActions(self, updates, repository):
"""
This method should be considered internal and not suited for general
audience.
This method insert "treeupdates" metadata in repository.
@param updates: new treeupdates metadata
@type updates: list
@param repository: insert treeupdates metadata for provided repository
@type repository: string
"""
raise NotImplementedError()
def setRepositoryUpdatesDigest(self, repository, digest):
"""
This method should be considered internal and not suited for general
audience.
Set "treeupdates" checksum (digest) for provided repository.
@param repository: repository identifier
@type repository: string
@param digest: treeupdates checksum string (md5)
@type digest: string
"""
raise NotImplementedError()
def addRepositoryUpdatesActions(self, repository, actions, branch):
"""
This method should be considered internal and not suited for general
audience.
Add "treeupdates" actions for repository and branch provided.
@param repository: repository identifier
@type repository: string
@param actions: list of raw treeupdates action strings
@type actions: list
@param branch: branch metadata to bind to the provided actions
@type branch: string
"""
raise NotImplementedError()
def clearPackageSets(self):
"""
Clear Package sets (group of packages) entries in repository.
"""
raise NotImplementedError()
def insertPackageSets(self, sets_data):
"""
Insert Package sets metadata into repository.
@param sets_data: dictionary containing package set names as keys and
list (set) of dependencies as value
@type sets_data: dict
"""
raise NotImplementedError()
def retrievePackageSets(self):
"""
Return Package sets metadata stored in repository.
@return: dictionary containing package set names as keys and
list (set) of dependencies as value
@rtype: dict
"""
raise NotImplementedError()
def retrievePackageSet(self, setname):
"""
Return dependencies belonging to given package set name.
This method does not check if the given package set name is
available and returns an empty list (set) in these cases.
@param setname: Package set name
@type setname: string
@return: list (set) of dependencies belonging to given package set name
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveAtom(self, package_id):
"""
Return "atom" metadatum for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: atom string
@rtype: string or None
"""
raise NotImplementedError()
def retrieveBranch(self, package_id):
"""
Return "branch" metadatum for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: branch metadatum
@rtype: string or None
"""
raise NotImplementedError()
def retrieveTrigger(self, package_id):
"""
Return "trigger" script content for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: trigger script content
@rtype: string or None
"""
raise NotImplementedError()
def retrieveDownloadURL(self, package_id):
"""
Return "download URL" metadatum for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: download url metadatum
@rtype: string or None
"""
raise NotImplementedError()
def retrieveDescription(self, package_id):
"""
Return "description" metadatum for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: package description
@rtype: string or None
"""
raise NotImplementedError()
def retrieveHomepage(self, package_id):
"""
Return "homepage" metadatum for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: package homepage
@rtype: string or None
"""
raise NotImplementedError()
def retrieveSpmUid(self, package_id):
"""
Return Source Package Manager unique identifier bound to Entropy
package identifier.
@param package_id: package indentifier
@type package_id: int
@return: Spm UID or -1 (if not bound, valid for injected packages)
@rtype: int
"""
raise NotImplementedError()
def retrieveSize(self, package_id):
"""
Return "size" metadatum for given package identifier.
"size" refers to Entropy package file size in bytes.
@param package_id: package indentifier
@type package_id: int
@return: size of Entropy package for given package identifier
@rtype: int or None
"""
raise NotImplementedError()
def retrieveOnDiskSize(self, package_id):
"""
Return "on disk size" metadatum for given package identifier.
"on disk size" refers to unpacked Entropy package file size in bytes,
which is in other words, the amount of space required on live system
to have it installed (simplified explanation).
@param package_id: package indentifier
@type package_id: int
@return: on disk size metadatum
@rtype: int
"""
raise NotImplementedError()
def retrieveDigest(self, package_id):
"""
Return "digest" metadatum for given package identifier.
"digest" refers to Entropy package file md5 checksum bound to given
package identifier.
@param package_id: package indentifier
@type package_id: int
@return: md5 checksum for given package identifier
@rtype: string or None
"""
raise NotImplementedError()
def retrieveSignatures(self, package_id):
"""
Return package file extra hashes (sha1, sha256, sha512) for given
package identifier.
@param package_id: package indentifier
@type package_id: int
@return: tuple of length 3, sha1, sha256, sha512 package extra
hashes if available, otherwise the same but with None as values.
@rtype: tuple
"""
raise NotImplementedError()
def retrieveExtraDownload(self, package_id, down_type = None):
"""
Retrieve a list of extra package file URLs for package identifier.
These URLs usually contain extra files that can be optionally installed
by Entropy Client, for example: debug files.
All the extra download file names must end with etpConst['packagesextraext']
extension.
@param package_id: package indentifier
@type package_id: int
@keyword down_type: retrieve data for a given entry type.
Currently supported entry types are: "debug", "data".
@type down_type: string
@return: list (tuple) of dict containing "download", "type", "size",
"disksize, "md5", "sha1","sha256", "sha512", "gpg" keys. "download"
contains the relative URL (like the one returned by
retrieveDownloadURL())
@rtype: tuple
@raise AttributeError: if provided down_type value is invalid
"""
raise NotImplementedError()
def retrieveName(self, package_id):
"""
Return "name" metadatum for given package identifier.
Attention: package name != atom, the former is just a subset of the
latter.
@param package_id: package indentifier
@type package_id: int
@return: "name" metadatum for given package identifier
@rtype: string or None
"""
raise NotImplementedError()
def retrieveKeySplit(self, package_id):
"""
Return a tuple composed by package category and package name for
given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: tuple of length 2 composed by (package_category, package_name,)
@rtupe: tuple or None
"""
raise NotImplementedError()
def retrieveKeySlot(self, package_id):
"""
Return a tuple composed by package key and slot for given package
identifier.
@param package_id: package indentifier
@type package_id: int
@return: tuple of length 2 composed by (package_key, package_slot,)
@rtupe: tuple or None
"""
raise NotImplementedError()
def retrieveKeySlotAggregated(self, package_id):
"""
Return package key and package slot string (aggregated form through
":", for eg.: app-foo/foo:2).
This method has been implemented for performance reasons.
@param package_id: package indentifier
@type package_id: int
@return: package key + ":" + slot string
@rtype: string or None
"""
raise NotImplementedError()
def retrieveKeySlotTag(self, package_id):
"""
Return package key, slot and tag tuple for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: tuple of length 3 providing (package_key, slot, package_tag,)
@rtype: tuple
"""
raise NotImplementedError()
def retrieveVersion(self, package_id):
"""
Return package version for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: package version
@rtype: string or None
"""
raise NotImplementedError()
def retrieveRevision(self, package_id):
"""
Return package Entropy-revision for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: Entropy-revision for given package indentifier
@rtype: int or None
"""
raise NotImplementedError()
def retrieveCreationDate(self, package_id):
"""
Return creation date for given package identifier.
Creation date returned is a string representation of UNIX time format.
@param package_id: package indentifier
@type package_id: int
@return: creation date for given package identifier
@rtype: string or None
"""
raise NotImplementedError()
def retrieveApi(self, package_id):
"""
Return Entropy API in use when given package identifier was added.
@param package_id: package indentifier
@type package_id: int
@return: Entropy API for given package identifier
@rtype: int or None
"""
raise NotImplementedError()
def retrieveUseflags(self, package_id):
"""
Return "USE flags" metadatum for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: list (frozenset) of USE flags for given package identifier.
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveSpmPhases(self, package_id):
"""
Return "Source Package Manager install phases" for given package
identifier.
@param package_id: package indentifier
@type package_id: int
@return: "Source Package Manager available install phases" string
@rtype: string or None
"""
raise NotImplementedError()
def retrieveSpmRepository(self, package_id):
"""
Return Source Package Manager source repository used at compile time.
@param package_id: package indentifier
@type package_id: int
@return: Source Package Manager source repository
@rtype: string or None
"""
raise NotImplementedError()
def retrieveDesktopMime(self, package_id):
"""
Return file association metadata for package.
@param package_id: package indentifier
@type package_id: int
@return: list of dict() containing file association information
@rtype: list
"""
raise NotImplementedError()
def retrieveProvidedMime(self, package_id):
"""
Return mime types associated to package. Mimetypes whose package
can handle.
@param package_id: package indentifier
@type package_id: int
@return: list (frozenset) of mimetypes
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveNeededRaw(self, package_id):
"""
Return (raw format) "NEEDED" ELF metadata for libraries contained
in given package.
@param package_id: package indentifier
@type package_id: int
@return: list (frozenset) of "NEEDED" entries contained in ELF objects
packed into package file
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveNeeded(self, package_id, extended = False, formatted = False):
"""
Return "NEEDED" elf metadata for libraries contained in given package.
@param package_id: package indentifier
@type package_id: int
@keyword extended: also return ELF class information for every
library name
@type extended: bool
@keyword formatted: properly format output, returning a dictionary with
library name as key and ELF class as value
@type formatted: bool
@return: "NEEDED" metadata for libraries contained in given package.
@rtype: tuple or dict
"""
raise NotImplementedError()
def retrieveProvidedLibraries(self, package_id):
"""
Return list of library names (from NEEDED ELF metadata) provided by
given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: list (frozenset) of tuples of length 3 composed by library
name, path and ELF class
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveConflicts(self, package_id):
"""
Return list of conflicting dependencies for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: list (frozenset) of conflicting package dependencies
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveProvide(self, package_id):
"""
Return list of dependencies/atoms are provided by the given package
identifier (see Portage documentation about old-style PROVIDEs).
@param package_id: package indentifier
@type package_id: int
@return: list (frozenset) of atoms provided by package
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveDependenciesList(self, package_id, exclude_deptypes = None,
resolve_conditional_deps = True):
"""
Return list of dependencies, including conflicts for given package
identifier.
@param package_id: package indentifier
@type package_id: int
@keyword exclude_deptypes: exclude given dependency types from returned
data. Please see etpConst['dependency_type_ids'] for valid values.
Anything != int will raise AttributeError
@type exclude_deptypes: list
@keyword resolve_conditional_deps: resolve conditional dependencies
automatically by default, stuff like
( app-foo/foo | app-foo/bar ) & bar-baz/foo
@type resolve_conditional_deps: bool
@return: list (frozenset) of dependencies of package
@rtype: frozenset
@raise AttributeError: if exclude_deptypes contains illegal values
"""
raise NotImplementedError()
def retrieveBuildDependencies(self, package_id, extended = False,
resolve_conditional_deps = True):
"""
Return list of build time package dependencies for given package
identifier.
Note: this function is just a wrapper of retrieveDependencies()
providing deptype (dependency type) = post-dependencies.
@param package_id: package indentifier
@type package_id: int
@keyword extended: return in extended format
@type extended: bool
@keyword resolve_conditional_deps: resolve conditional dependencies
automatically by default, stuff like
( app-foo/foo | app-foo/bar ) & bar-baz/foo
@type resolve_conditional_deps: bool
@return: list (frozenset) of build dependencies of package
@rtype: frozenset
"""
raise NotImplementedError()
def retrievePostDependencies(self, package_id, extended = False,
resolve_conditional_deps = True):
"""
Return list of post-merge package dependencies for given package
identifier.
Note: this function is just a wrapper of retrieveDependencies()
providing deptype (dependency type) = post-dependencies.
@param package_id: package indentifier
@type package_id: int
@keyword extended: return in extended format
@type extended: bool
@keyword resolve_conditional_deps: resolve conditional dependencies
automatically by default, stuff like
( app-foo/foo | app-foo/bar ) & bar-baz/foo
@type resolve_conditional_deps: bool
@return: list (frozenset) of post dependencies of package
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveManualDependencies(self, package_id, extended = False,
resolve_conditional_deps = True):
"""
Return manually added dependencies for given package identifier.
Note: this function is just a wrapper of retrieveDependencies()
providing deptype (dependency type) = manual-dependencies.
@param package_id: package indentifier
@type package_id: int
@keyword extended: return in extended format
@type extended: bool
@keyword resolve_conditional_deps: resolve conditional dependencies
automatically by default, stuff like
( app-foo/foo | app-foo/bar ) & bar-baz/foo
@type resolve_conditional_deps: bool
@return: list (frozenset) of manual dependencies of package
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveDependencies(self, package_id, extended = False, deptype = None,
exclude_deptypes = None, resolve_conditional_deps = True):
"""
Return dependencies for given package identifier.
@param package_id: package indentifier
@type package_id: int
@keyword extended: return in extended format (list of tuples of length 2
composed by dependency name and dependency type)
@type extended: bool
@keyword deptype: return only given type of dependencies
see etpConst['dependency_type_ids']['*depend_id'] for dependency type
identifiers
@type deptype: bool
@keyword exclude_deptypes: exclude given dependency types from returned
data. Please see etpConst['dependency_type_ids'] for valid values.
Anything != int will raise AttributeError
@type exclude_deptypes: list
@keyword resolve_conditional_deps: resolve conditional dependencies
automatically by default, stuff like
( app-foo/foo | app-foo/bar ) & bar-baz/foo
@type resolve_conditional_deps: bool
@return: dependencies of given package
@rtype: tuple or frozenset
@raise AttributeError: if exclude_deptypes contains illegal values
"""
raise NotImplementedError()
def retrieveKeywords(self, package_id):
"""
Return package SPM keyword list for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: list (frozenset) of keywords for given package identifier
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveProtect(self, package_id):
"""
Return CONFIG_PROTECT (configuration file protection) string
(containing a list of space reparated paths) metadata for given
package identifier.
@param package_id: package indentifier
@type package_id: int
@return: CONFIG_PROTECT string
@rtype: string
"""
raise NotImplementedError()
def retrieveProtectMask(self, package_id):
"""
Return CONFIG_PROTECT_MASK (mask for configuration file protection)
string (containing a list of space reparated paths) metadata for given
package identifier.
@param package_id: package indentifier
@type package_id: int
@return: CONFIG_PROTECT_MASK string
@rtype: string
"""
raise NotImplementedError()
def retrieveSources(self, package_id, extended = False):
"""
Return source package URLs for given package identifier.
"source" as in source code.
@param package_id: package indentifier
@type package_id: int
@keyword extended:
@type extended: bool
@return: if extended is True, dict composed by source URLs as key
and list of mirrors as value, otherwise just a list (frozenset) of
source package URLs.
@rtype: dict or frozenset
"""
raise NotImplementedError()
def retrieveAutomergefiles(self, package_id, get_dict = False):
"""
Return previously merged protected configuration files list and
their md5 hashes for given package identifier.
This is part of the "automerge" feature which uses file md5 checksum
to determine if a protected configuration file can be merged auto-
matically.
@param package_id: package indentifier
@type package_id: int
@keyword get_dict: return a dictionary with configuration file as key
and md5 hash as value
@type get_dict: bool
@return: automerge metadata for given package identifier
@rtype: frozenset or dict
"""
raise NotImplementedError()
def retrieveContent(self, package_id, extended = False,
formatted = False, insert_formatted = False, order_by = None):
"""
Return files contained in given package.
@param package_id: package indentifier
@type package_id: int
@keyword extended: return in extended format
@type extended: bool
@keyword formatted: return in dict() form
@type formatted: bool
@keyword insert_formatted: return in list of tuples form, ready to
be added with insertContent()
@keyword order_by: order by string, valid values are:
"type" (if extended is True), "file" or "package_id"
@type order_by: string
@return: content metadata
@rtype: dict or tuple or frozenset
@raise AttributeError: if order_by value is invalid
"""
raise NotImplementedError()
def retrieveContentSafety(self, package_id):
"""
Return supported content safety metadata for given package.
Data returned is a dictionary, using package file path as key and
dictionary as value. The latter, contains supported SPM content
safety metadata, such as "sha256" (string) checksum, and "mtime"
(float). The barely minimum is in fact, supporting sha256 and mtime
of package files.
@param package_id: package indentifier
@type package_id: int
@return: content safety metadata
@rtype: dict
"""
raise NotImplementedError()
def retrieveChangelog(self, package_id):
"""
Return Source Package Manager ChangeLog for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: ChangeLog content
@rtype: string or None
"""
raise NotImplementedError()
def retrieveChangelogByKey(self, category, name):
"""
Return Source Package Manager ChangeLog content for given package
category and name.
@param category: package category
@type category: string
@param name: package name
@type name: string
@return: ChangeLog content
@rtype: string or None
"""
raise NotImplementedError()
def retrieveSlot(self, package_id):
"""
Return "slot" metadatum for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: package slot
@rtype: string or None
"""
raise NotImplementedError()
def retrieveTag(self, package_id):
"""
Return "tag" metadatum for given package identifier.
Tagging packages allows, for example, to support multiple
different, colliding atoms in the same repository and still being
able to exactly reference them. It's actually used to provide
versions of external kernel modules for different kernels.
@param package_id: package indentifier
@type package_id: int
@return: tag string
@rtype: string or None
"""
raise NotImplementedError()
def retrieveMirrorData(self, mirrorname):
"""
Return available mirror URls for given mirror name.
@param mirrorname: mirror name (for eg. "openoffice")
@type mirrorname: string
@return: list (frozenset) of URLs providing the "openoffice"
mirroring service
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveCategory(self, package_id):
"""
Return category name for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: category where package is in
@rtype: string or None
"""
raise NotImplementedError()
def retrieveCategoryDescription(self, category):
"""
Return description text for given category.
@param category: category name
@type category: string
@return: category description dict, locale as key, description as value
@rtype: dict
"""
raise NotImplementedError()
def retrieveLicenseData(self, package_id):
"""
Return license metadata for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: dictionary composed by license name as key and license text
as value
@rtype: dict
"""
raise NotImplementedError()
def retrieveLicenseDataKeys(self, package_id):
"""
Return license names available for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: list (frozenset) of license names which text is available in
repository
@rtype: frozenset
"""
raise NotImplementedError()
def retrieveLicenseText(self, license_name):
"""
Return license text for given license name.
@param license_name: license name (for eg. GPL-2)
@type license_name: string
@return: license text
@rtype: string (raw format) or None
"""
raise NotImplementedError()
def retrieveLicense(self, package_id):
"""
Return "license" metadatum for given package identifier.
@param package_id: package indentifier
@type package_id: int
@return: license string
@rtype: string or None
"""
raise NotImplementedError()
def retrieveCompileFlags(self, package_id):
"""
Return Compiler flags during building of package.
(CHOST, CXXFLAGS, LDFLAGS)
@param package_id: package indentifier
@type package_id: int
@return: tuple of length 3 composed by (CHOST, CFLAGS, CXXFLAGS)
@rtype: tuple
"""
raise NotImplementedError()
def retrieveReverseDependencies(self, package_id, atoms = False,
key_slot = False, exclude_deptypes = None, extended = False):
"""
Return reverse (or inverse) dependencies for given package.
@param package_id: package indentifier
@type package_id: int
@keyword atoms: if True, method returns list of atoms
@type atoms: bool
@keyword key_slot: if True, method returns list of dependencies in
key:slot form, example: (('app-foo/bar','2',), ...)
@type key_slot: bool
@keyword exclude_deptypes: exclude given dependency types from returned
data. Please see etpConst['dependency_type_ids'] for valid values.
Anything != int will raise AttributeError
@type exclude_deptypes: iterable of ints
@keyword extended: if True, the original dependency string will
be returned along with the rest of information. So, if data
returned would be a list of package identifiers (int),
if extended = True this method will return a list of tuples
composed by (package_id, dep_string). Same for atoms = True and
key_slot = True.
@type extended: bool
@return: reverse dependency list (tuple) (or list of lists in case
of extended = True)
@rtype: tuple or frozenset
@raise AttributeError: if exclude_deptypes contains illegal values
"""
raise NotImplementedError()
def retrieveUnusedPackageIds(self):
"""
Return packages (through their identifiers) not referenced by any
other as dependency (unused packages).
@return: unused package_ids ordered by atom
@rtype: tuple
"""
raise NotImplementedError()
def arePackageIdsAvailable(self, package_ids):
"""
Return whether list of package identifiers are available.
They must be all available to return True
@param package_ids: list of package indentifiers
@type package_ids: iterable
@return: availability (True if all are available)
@rtype: bool
"""
raise NotImplementedError()
def isPackageIdAvailable(self, package_id):
"""
Return whether given package identifier is available in repository.
@param package_id: package indentifier
@type package_id: int
@return: availability (True if available)
@rtype: bool
"""
raise NotImplementedError()
def isFileAvailable(self, path, get_id = False):
"""
Return whether given file path is available in repository (owned by
one or more packages).
@param path: path to file or directory
@type path: string
@keyword get_id: return list (set) of package_ids owning myfile
@type get_id: bool
@return: availability (True if available), when get_id is True,
it returns a list (frozenset) of package_ids owning myfile
@rtype: bool or frozenset
"""
raise NotImplementedError()
def resolveNeeded(self, needed, elfclass = -1, extended = False):
"""
Resolve NEEDED ELF entry (a library name) to package_ids owning given
needed (stressing, needed = library name)
@param needed: library name
@type needed: string
@keyword elfclass: look for library name matching given ELF class
@type elfclass: int
@keyword extended: return a frozenset of tuple of length 2, first
element is package_id, second is actual library path
@type extended: bool
@return: list of packages owning given library
@rtype: frozenset
"""
raise NotImplementedError()
def isNeededAvailable(self, needed):
"""
Return whether NEEDED ELF entry (library name) is available in
repository.
Returns NEEDED entry identifier
@param needed: NEEDED ELF entry (library name)
@type needed: string
@return: NEEDED entry identifier or -1 if not found
@rtype: int
"""
raise NotImplementedError()
def isSpmUidAvailable(self, spm_uid):
"""
Return whether Source Package Manager package identifier is available
in repository.
@param spm_uid: Source Package Manager package identifier
@type spm_uid: int
@return: availability (True, if available)
@rtype: bool
"""
raise NotImplementedError()
def isSpmUidTrashed(self, spm_uid):
"""
Return whether Source Package Manager package identifier has been
trashed. One is trashed when it gets removed from a repository while
still sitting there in place on live system. This is a trick to allow
multiple-repositories management to work fine when shitting around.
@param spm_uid: Source Package Manager package identifier
@type spm_uid: int
@return: availability (True, if available)
@rtype: bool
"""
raise NotImplementedError()
def isLicenseDataKeyAvailable(self, license_name):
"""
Return whether license name is available in License database, which is
the one containing actual license texts.
@param license_name: license name which license text is available
@type license_name: string
@return: availability (True, if available)
@rtype: bool
"""
raise NotImplementedError()
def isLicenseAccepted(self, license_name):
"""
Return whether given license (through its name) has been accepted by
user.
@param license_name: license name
@type license_name: string
@return: if license name has been accepted by user
@rtype: bool
"""
raise NotImplementedError()
def acceptLicense(self, license_name):
"""
Mark license name as accepted by user.
Only and only if user is allowed to accept them:
- in entropy group
- db not open in read only mode
Attention: call this method from your subclass, otherwise
EntropyRepositoryPlugins won't be notified.
@param license_name: license name
@type license_name: string
"""
plugins = self.get_plugins()
for plugin_id in sorted(plugins):
plug_inst = plugins[plugin_id]
exec_rc = plug_inst.accept_license_hook(self)
if exec_rc:
raise RepositoryPluginError(
"[accept_license_hook] %s: status: %s" % (
plug_inst.get_id(), exec_rc,))
def isSystemPackage(self, package_id):
"""
Return whether package is part of core system (though, a system
package).
@param package_id: package indentifier
@type package_id: int
@return: if True, package is part of core system
@rtype: bool
"""
raise NotImplementedError()
def isInjected(self, package_id):
"""
Return whether package has been injected into repository (means that
will be never ever removed due to colliding scope when other
packages will be added).
@param package_id: package indentifier
@type package_id: int
@return: injection status (True if injected)
@rtype: bool
"""
raise NotImplementedError()
def searchProvidedVirtualPackage(self, keyword):
"""
Search in old-style Portage PROVIDE metadata.
@todo: rewrite docstring :-)
@param keyword: search term
@type keyword: string
@return: found PROVIDE metadata
@rtype: list
"""
raise NotImplementedError()
def searchBelongs(self, bfile, like = False):
"""
Search packages which given file path belongs to.
@param bfile: file path to search
@type bfile: string
@keyword like: do not match exact case
@type like: bool
@return: list (frozenset) of package identifiers owning given file
@rtype: frozenset
"""
raise NotImplementedError()
def searchContentSafety(self, sfile):
"""
Search content safety metadata (usually, sha256 and mtime) related to
given file path. A list of dictionaries is returned, each dictionary
item contains at least the following fields "package_id", "path",
"sha256", "mtime").
@param sfile: file path to search
@type sfile: string
@return: content safety metadata list (tuple)
@rtype: tuple
"""
raise NotImplementedError()
def searchTaggedPackages(self, tag, atoms = False):
"""
Search packages which "tag" metadatum matches the given one.
@param tag: tag name to search
@type tag: string
@keyword atoms: return list of atoms instead of package identifiers
@type atoms: bool
@return: list of packages using given tag
@rtype: frozenset
"""
raise NotImplementedError()
def searchRevisionedPackages(self, revision):
"""
Search packages which "revision" metadatum matches the given one.
@param revision: Entropy revision to search
@type revision: string
@return: list (frozenset) of packages using given tag
@rtype: frozenset
"""
raise NotImplementedError()
def searchLicense(self, keyword, just_id = False):
"""
Search packages using given license (mylicense).
@param keyword: license name to search
@type keyword: string
@keyword just_id: just return package identifiers, otherwise a frozenset
of tuples of length 2 is returned
@type just_id: bool
@return: list (frozenset) of packages using given license
@rtype: frozenset
"""
raise NotImplementedError()
def searchSlotted(self, keyword, just_id = False):
"""
Search packages with given slot string.
@param keyword: slot to search
@type keyword: string
@keyword just_id: just return package identifiers, otherwise a frozenset
of tuples of length 2 is returned
@type just_id: bool
@return: list (frozenset) of packages using given slot
@rtype: frozenset
"""
raise NotImplementedError()
def searchKeySlot(self, key, slot):
"""
Search package with given key and slot
@param key: package key
@type key: string
@param slot: package slot
@type slot: string
@return: list (frozenset) of package identifiers
@rtype: frozenset
"""
raise NotImplementedError()
def searchNeeded(self, needed, elfclass = -1, like = False):
"""
Search packages that need given NEEDED ELF entry (library name).
You must implement "*" wildcard support if like is True.
@param needed: NEEDED ELF entry (shared object library name)
@type needed: string
@param elfclass: search NEEDEDs only with given ELF class
@type elfclass: int
@keyword like: do not match exact case
@type like: bool
@return: list (frozenset) of package identifiers
@rtype: frozenset
"""
raise NotImplementedError()
def searchConflict(self, conflict, strings = False):
"""
Search conflict dependency among packages.
@param conflict:
@type conflict: string
@keyword strings: return a list of conflict names instead of a tuple of
tuples of entropy package identifier and conflict dependency.
@type strings: bool
@return: if "strings" is False, a tuple of tuples of entropy package
identifier and conflict dependency. if "strings" is True, a list
(tuple) of conflict dependencies.
@rtype tuple
"""
raise NotImplementedError()
def searchDependency(self, dep, like = False, multi = False,
strings = False):
"""
Search dependency name in repository.
Returns dependency identifier (iddependency) or dependency strings
(if strings argument is True).
@param dep: dependency name
@type dep: string
@keyword like: do not match exact case
@type like: bool
@keyword multi: return all the matching dependency names
@type multi: bool
@keyword strings: return dependency names rather than dependency
identifiers
@type strings: bool
@return: list (frozenset) of dependency identifiers (if multi is True)
or strings (if strings is True) or dependency identifier
@rtype: int or frozenset
"""
raise NotImplementedError()
def searchPackageIdFromDependencyId(self, dependency_id):
"""
Search package identifiers owning dependency given (in form of
dependency identifier).
@param dependency_id: dependency identifier
@type dependency_id: int
@return: list (frozenset) of package identifiers owning given dependency
identifier
@rtype: frozenset
"""
raise NotImplementedError()
def searchSets(self, keyword):
"""
Search package sets in repository using given search keyword.
@param keyword: package set name to search
@type keyword: string
@return: list (frozenset) of package sets available matching given
keyword
@rtype: frozenset
"""
raise NotImplementedError()
def searchProvidedMime(self, mimetype):
"""
Search package identifiers owning given mimetype. Results are returned
sorted by package name.
@param mimetype: mimetype to search
@type mimetype: string
@return: list (tuple) of package indentifiers owning given mimetype.
@rtype: tuple
"""
raise NotImplementedError()
def searchSimilarPackages(self, keyword, atom = False):
"""
Search similar packages (basing on package string given by mystring
argument) using SOUNDEX algorithm.
@param keyword: package string to search
@type keyword: string
@keyword atom: return full atoms instead of package names
@type atom: bool
@return: list (tuple) of similar package names
@rtype: tuple
"""
raise NotImplementedError()
def searchPackages(self, keyword, sensitive = False, slot = None,
tag = None, order_by = None, just_id = False):
"""
Search packages using given package name "keyword" argument.
@param keyword: package string
@type keyword: string
@keyword sensitive: case sensitive?
@type sensitive: bool
@keyword slot: search matching given slot
@type slot: string
@keyword tag: search matching given package tag
@type tag: string
@keyword order_by: order results by "atom", "package_id", "branch",
"name", "version", "versiontag", "revision", "slot"
@type order_by: string
@keyword just_id: just return package identifiers
@type just_id: bool
@return: packages found matching given search criterias
@rtype: tuple
@raise AttributeError: if order_by value is invalid
"""
raise NotImplementedError()
def searchDescription(self, keyword, just_id = False):
"""
Search packages using given description string as keyword.
@param keyword: description sub-string to search
@type keyword: string
@keyword just_id: if True, only return a list of Entropy package
identifiers
@type just_id: bool
@return: frozenset of tuples of length 2 containing atom and package_id
values. While if just_id is True, return a list (frozenset) of
package_ids
@rtype: frozenset
"""
raise NotImplementedError()
def searchUseflag(self, keyword, just_id = False):
"""
Search packages using given use flag string as keyword. An exact search
will be performed (keyword must match use flag)
@param keyword: use flag to search
@type keyword: string
@keyword just_id: if True, only return a list of Entropy package
identifiers
@type just_id: bool
@return: frozenset of tuples of length 2 containing atom and package_id
values. While if just_id is True, return a list (frozenset) of
package_ids
@rtype: frozenset
"""
raise NotImplementedError()
def searchHomepage(self, keyword, just_id = False):
"""
Search packages using given homepage string as keyword.
@param keyword: description sub-string to search
@type keyword: string
@keyword just_id: if True, only return a list of Entropy package
identifiers
@type just_id: bool
@return: frozenset of tuples of length 2 containing atom and package_id
values. While if just_id is True, return a list (frozenset) of
package_ids
@rtype: frozenset
"""
raise NotImplementedError()
def searchName(self, keyword, sensitive = False, just_id = False):
"""
Search packages by package name.
@param keyword: package name to search
@type keyword: string
@keyword sensitive: case sensitive?
@type sensitive: bool
@keyword just_id: return list of package identifiers (set()) otherwise
return a list of tuples of length 2 containing atom and package_id
values
@type just_id: bool
@return: list (frozenset) of packages found
@rtype: frozenset
"""
raise NotImplementedError()
def searchCategory(self, keyword, like = False, just_id = False):
"""
Search packages by category name.
@param keyword: category name
@type keyword: string
@keyword like: do not match exact case
@type like: bool
@keyword just_id: return list of package identifiers (set()) otherwise
return a list of tuples of length 2 containing atom and package_id
values
@type just_id: bool
@return: list (frozenset) of tuples of length 2 containing atom and
package_id values
@rtype: frozenset
"""
raise NotImplementedError()
def searchNameCategory(self, name, category, just_id = False):
"""
Search packages matching given name and category strings.
The search is always considered case sensitive.
@param name: package name to search
@type name: string
@param category: package category to search
@type category: string
@keyword just_id: return list of package identifiers (set()) otherwise
return a list of tuples of length 2 containing atom and package_id
values
@type just_id: bool
@return: list (frozenset) of packages found
@rtype: frozenset
"""
raise NotImplementedError()
def isPackageScopeAvailable(self, atom, slot, revision):
"""
Return whether given package scope is available.
Also check if package found is masked and return masking reason
identifier.
@param atom: package atom string
@type atom: string
@param slot: package slot string
@type slot: string
@param revision: entropy package revision
@type revision: int
@return: tuple composed by (package_id or -1, idreason or 0,)
@rtype: tuple
"""
raise NotImplementedError()
def isBranchMigrationAvailable(self, repository, from_branch, to_branch):
"""
Returns whether branch migration metadata given by the provided key
(repository, from_branch, to_branch,) is available.
@param repository: repository identifier
@type repository: string
@param from_branch: original branch
@type from_branch: string
@param to_branch: destination branch
@type to_branch: string
@return: tuple composed by (1)post migration script md5sum and
(2)post upgrade script md5sum
@rtype: tuple
"""
raise NotImplementedError()
def listAllPackages(self, get_scope = False, order_by = None):
"""
List all packages in repository.
@keyword get_scope: return also entropy package revision
@type get_scope: bool
@keyword order_by: order by "atom", "idpackage", "package_id", "branch",
"name", "version", "versiontag", "revision", "slot"
@type order_by: string
@return: tuple of tuples of length 3 (or 4 if get_scope is True),
containing (atom, package_id, branch,) if get_scope is False and
(package_id, atom, slot, revision,) if get_scope is True
@rtype: tuple
@raise AttributeError: if order_by value is invalid
"""
raise NotImplementedError()
def listPackageIdsInCategory(self, category, order_by = None):
"""
List package identifiers available in given category name.
@param category_id: cateogory name
@type category_id: int
@keyword order_by: order by "atom", "idpackage", "package_id", "branch",
"name", "version", "versiontag", "revision", "slot"
@type order_by: string
@return: list (frozenset) of available package identifiers in category.
@rtype: frozenset
@raise AttributeError: if order_by value is invalid
"""
raise NotImplementedError()
def listAllPackageIds(self, order_by = None):
"""
List all package identifiers available in repository.
@keyword order_by: order by "atom", "idpackage", "package_id", "branch",
"name", "version", "versiontag", "revision", "slot", "date"
@type order_by: string
@return: tuple (if order_by) or frozenset of package identifiers
@rtype: tuple or frozenset
@raise AttributeError: if order_by value is invalid
"""
def listAllSpmUids(self):
"""
List all Source Package Manager unique package identifiers bindings
with packages in repository.
@return: tuple of tuples of length 2 composed by (spm_uid, package_id,)
@rtype: tuple
"""
raise NotImplementedError()
def listAllDownloads(self, do_sort = True, full_path = False):
"""
List all package download URLs stored in repository.
@keyword do_sort: sort by name
@type do_sort: bool
@keyword full_path: return full URL (not just package file name)
@type full_path: bool
@return: tuple (or set if do_sort is True) of package download URLs
@rtype: tuple or frozenset
"""
raise NotImplementedError()
def listAllExtraDownloads(self, do_sort = True):
"""
List all package extra download URLs stored in repository.
All the extra download file names must end with etpConst['packagesextraext']
extension.
@keyword do_sort: sort by name
@type do_sort: bool
@keyword full_path: return full URL (not just package file name)
@type full_path: bool
@return: tuple (or set if do_sort is True) of package download URLs
@rtype: tuple or frozenset
"""
raise NotImplementedError()
def listAllFiles(self, clean = False, count = False):
"""
List all file paths owned by packaged stored in repository.
@keyword clean: return a clean list (not duplicates)
@type clean: bool
@keyword count: count elements and return number
@type count: bool
@return: tuple of files available or their count
@rtype: int or tuple or frozenset
"""
raise NotImplementedError()
def listAllCategories(self, order_by = None):
"""
List all categories available in repository.
@keyword order_by: order by "category"
@type order_by: string
@return: list (frozenset) of available package categories
@rtype: frozenset
@raise AttributeError: if order_by value is invalid
"""
raise NotImplementedError()
def listConfigProtectEntries(self, mask = False):
"""
List CONFIG_PROTECT* entries (configuration file/directories
protection).
@keyword mask: return CONFIG_PROTECT_MASK metadata instead of
CONFIG_PROTECT
@type mask: bool
@return: list of protected/masked directories
@rtype: list
"""
raise NotImplementedError()
def switchBranch(self, package_id, tobranch):
"""
Switch branch string in repository to new value.
@param package_id: package identifier
@type package_id: int
@param tobranch: new branch value
@type tobranch: string
"""
raise NotImplementedError()
def getSetting(self, setting_name):
"""
Return stored Repository setting.
For currently supported setting_name values look at
EntropyRepository._SETTING_KEYS.
@param setting_name: name of repository setting
@type setting_name: string
@return: setting value
@rtype: string
@raise KeyError: if setting_name is not valid or available
"""
raise NotImplementedError()
def validate(self):
"""
Validates Entropy repository by doing basic integrity checks.
@raise SystemDatabaseError: when repository is not reliable
"""
raise NotImplementedError()
def integrity_check(self):
"""
Validates Entropy repository by doing advanced integrity checks.
@raise SystemDatabaseError: when repository is not reliable
"""
raise NotImplementedError()
def alignDatabases(self, dbconn, force = False, output_header = " ",
align_limit = 300):
"""
Align packages contained in foreign repository "dbconn" and this
instance.
@param dbconn: foreign repository instance
@type dbconn: entropy.db.EntropyRepository
@keyword force: force alignment even if align_limit threshold is
exceeded
@type force: bool
@keyword output_header: output header for printing purposes
@type output_header: string
@keyword align_limit: threshold within alignment is done if force is
False
@type align_limit: int
@return: alignment status (0 = all good; 1 = dbs checksum not matching;
-1 = nothing to do)
@rtype: int
"""
raise NotImplementedError()
@staticmethod
def importRepository(dumpfile, dbfile):
"""
Import SQLite3 dump file to this database.
@param dumpfile: SQLite3 dump file to read
@type dumpfile: string
@param dbfile: database file to write to
@type dbfile: string
@return: sqlite3 import return code
@rtype: int
@raise AttributeError: if given paths are invalid
"""
raise NotImplementedError()
def exportRepository(self, dumpfile):
"""
Export running SQLite3 database to file.
@param dumpfile: dump file object to write to
@type dumpfile: file object (hint: open())
"""
raise NotImplementedError()
def checksum(self, do_order = False, strict = True,
strings = True, include_signatures = False):
"""
Get Repository metadata checksum, useful for integrity verification.
Note: result is cached in EntropyRepository.live_cache (dict).
@keyword do_order: order metadata collection alphabetically
@type do_order: bool
@keyword strict: improve checksum accuracy
@type strict: bool
@keyword strings: return checksum in md5 hex form
@type strings: bool
@keyword include_signatures: also include packages signatures (GPG,
SHA1, SHA2, etc) into returned hash
@type include_signatures: bool
@return: repository checksum
@rtype: string
"""
raise NotImplementedError()
def mtime(self):
"""
Return last modification time of given repository.
@return: mtime
@rtype: float
@raise IOError: if mtime cannot be retrieved
@raise OSError: if mtime cannot be retrieved (Operating System error)
"""
raise NotImplementedError()
def storeInstalledPackage(self, package_id, repoid, source = 0):
"""
Note: this is used by installed packages repository (also known as
client db).
Add package identifier to the "installed packages table",
which contains repository identifier from where package has been
installed and its install request source (user, pulled in
dependency, etc).
@param package_id: package indentifier
@type package_id: int
@param repoid: repository identifier
@type repoid: string
@param source: source identifier (pleas see:
etpConst['install_sources'])
@type source: int
"""
raise NotImplementedError()
def getInstalledPackageRepository(self, package_id):
"""
Return repository identifier from where package has been installed from.
@param package_id: package indentifier
@type package_id: int
@return: repository identifier
@rtype: string or None
"""
raise NotImplementedError()
def getInstalledPackageSource(self, package_id):
"""
Return installed package source id (corresponding to "as dependency",
"by user", in other words, the reason why the package is installed).
Its value can be either one of the etpConst['install_sources'] values.
In case of unavailable information, None is returned.
@param package_id: package indentifier
@type package_id: int
@return: install source identifier
@rtype: int or None
"""
raise NotImplementedError()
def dropInstalledPackageFromStore(self, package_id):
"""
Note: this is used by installed packages repository (also known as
client db).
Remove installed package metadata from "installed packages table".
Note: this just removes extra metadata information such as repository
identifier from where package has been installed and its install
request source (user, pulled in dependency, etc).
This method DOES NOT remove package from repository (see
removePackage() instead).
@param package_id: package indentifier
@type package_id: int
"""
raise NotImplementedError()
def storeSpmMetadata(self, package_id, blob):
"""
This method stores Source Package Manager package metadata inside
repository.
@param package_id: package indentifier
@type package_id: int
@param blob: metadata blob
@type blob: string or buffer
"""
raise NotImplementedError()
def retrieveSpmMetadata(self, package_id):
"""
This method retrieves Source Package Manager package metadata stored
inside repository.
@param package_id: package indentifier
@type package_id: int
@return: stored metadata
@rtype: buffer
"""
raise NotImplementedError()
def retrieveBranchMigration(self, to_branch):
"""
This method returns branch migration metadata stored in Entropy
Client database (installed packages database). It is used to
determine whether to run per-repository branch migration scripts.
@param to_branch: usually the current branch string
@type to_branch: string
@return: branch migration metadata contained in database
@rtype: dict
"""
raise NotImplementedError()
def dropContent(self):
"""
Drop all "content" metadata from repository, usually a memory hog.
Content metadata contains files and directories owned by packages.
"""
raise NotImplementedError()
def dropContentSafety(self):
"""
Drop all "contentsafety" metadata from repository, usually a memory hog.
ContentSafety metadata contains mtime and sha256 hashes of files owned
by package.
"""
raise NotImplementedError()
def dropChangelog(self):
"""
Drop all packages' ChangeLogs metadata from repository, a memory hog.
"""
raise NotImplementedError()
def dropGpgSignatures(self):
"""
Drop all packages' GPG signatures.
"""
raise NotImplementedError()
def dropAllIndexes(self):
"""
Drop all repository metadata indexes. Not cache!
"""
raise NotImplementedError()
def createAllIndexes(self):
"""
Create all the repository metadata indexes internally available.
"""
raise NotImplementedError()
def regenerateSpmUidMapping(self):
"""
Regenerate Source Package Manager <-> Entropy package identifiers
mapping.
This method will use the Source Package Manger interface.
"""
raise NotImplementedError()
def clearTreeupdatesEntries(self, repository):
"""
This method should be considered internal and not suited for general
audience. Clear "treeupdates" metadata for given repository identifier.
@param repository: repository identifier
@type repository: string
"""
raise NotImplementedError()
def resetTreeupdatesDigests(self):
"""
This method should be considered internal and not suited for general
audience. Reset "treeupdates" digest metadata.
"""
raise NotImplementedError()
def moveSpmUidsToBranch(self, to_branch):
"""
Note: this is not intended for general audience.
Move "branch" metadata contained in Source Package Manager package
identifiers binding metadata to new value given by "from_branch"
argument.
@param to_branch: new branch string
@type to_branch: string
@keyword from_branch: old branch string
@type from_branch: string
"""
raise NotImplementedError()
# Update status flags, self explanatory.
REPOSITORY_ALREADY_UPTODATE = -1
REPOSITORY_NOT_AVAILABLE = -2
REPOSITORY_GENERIC_ERROR = -3
REPOSITORY_CHECKSUM_ERROR = -4
REPOSITORY_PERMISSION_DENIED_ERROR = -5
REPOSITORY_UPDATED_OK = 0
@staticmethod
def update(entropy_client, repository_id, force, gpg):
"""
Update the content of this repository. Every subclass can implement
its own update way.
This method must return a status code that can be either
EntropyRepositoryBase.REPOSITORY_ALREADY_UPTODATE or
EntropyRepositoryBase.REPOSITORY_NOT_AVAILABLE or
EntropyRepositoryBase.REPOSITORY_GENERIC_ERROR or
EntropyRepositoryBase.REPOSITORY_CHECKSUM_ERROR or
EntropyRepositoryBase.REPOSITORY_UPDATED_OK
If your repository is not supposed to be remotely updated, just
ignore this method.
Otherwise, if you intend to implement this method, make sure that
any unprivileged call raises entropy.exceptions.PermissionDenied().
Only superuser should call this method.
@param entropy_client: Entropy Client based object
@type entropy_client: entropy.client.interfaces.Client
@param repository_id: repository identifier
@type repository_id: string
@param force: force update anyway
@type force: bool
@param gpg: GPG feature enable
@type gpg: bool
@return: status code
@rtype: int
"""
raise NotImplementedError()
@staticmethod
def revision(repository_id):
"""
Returns the repository local revision in int format or None, if
no revision is available.
@param repository_id: repository identifier
@type repository_id: string
@return: repository revision
@rtype: int or None
@raise KeyError: if repository is not available
"""
raise NotImplementedError()
@staticmethod
def remote_revision(repository_id):
"""
Returns the repository remote revision in int format or None, if
no revision is available.
@param repository_id: repository identifier
@type repository_id: string
@return: repository revision
@rtype: int or None
@raise KeyError: if repository is not available
"""
raise NotImplementedError()
def maskFilter(self, package_id, live = True):
"""
Return whether given package identifier is available to user or not,
reading package masking metadata stored in SystemSettings.
NOTE: by default, this method doesn't filter any package. Subclasses
have to reimplement this and setup a filtering logic in order to apply
some filtering.
@param package_id: package indentifier
@type package_id: int
@keyword live: use live masking feature
@type live: bool
@return: tuple composed by package_id and masking reason. If package_id
returned package_id value == -1, it means that package is masked
and a valid masking reason identifier is returned as second
value of the tuple (see SystemSettings['pkg_masking_reasons'])
@rtype: tuple
"""
return package_id, 0
def atomMatch(self, atom, matchSlot = None, multiMatch = False,
maskFilter = True, extendedResults = False, useCache = True):
"""
Match given atom (or dependency) in repository and return its package
identifer and execution status.
@param atom: atom or dependency to match in repository
@type atom: unicode string
@keyword matchSlot: match packages with given slot
@type matchSlot: string
@keyword multiMatch: match all the available packages, not just the
best one
@type multiMatch: bool
@keyword maskFilter: enable package masking filter
@type maskFilter: bool
@keyword extendedResults: return extended results
@type extendedResults: bool
@keyword useCache: use on-disk cache
@type useCache: bool
@return: tuple of length 2 composed by (package_id or -1, command status
(0 means found, 1 means error)) or, if extendedResults is True,
also add versioning information to tuple.
If multiMatch is True, a tuple composed by a set (containing package
identifiers) and command status is returned.
@rtype: tuple or set
"""
if not atom:
return -1, 1
if useCache:
cached = self.__atomMatchFetchCache(atom, matchSlot,
multiMatch, maskFilter, extendedResults)
if cached is not None:
try:
cached = self.__atomMatchValidateCache(cached,
multiMatch, extendedResults)
except (TypeError, ValueError, IndexError, KeyError,):
cached = None
if cached is not None:
return cached
# "or" dependency support
# app-foo/foo-1.2.3;app-foo/bar-1.4.3?
if atom.endswith(etpConst['entropyordepquestion']):
# or dependency!
atoms = atom[:-1].split(etpConst['entropyordepsep'])
for s_atom in atoms:
data, rc = self.atomMatch(s_atom, matchSlot = matchSlot,
multiMatch = multiMatch, maskFilter = maskFilter,
extendedResults = extendedResults, useCache = useCache)
if rc == 0:
return data, rc
matchTag = entropy.dep.dep_gettag(atom)
try:
matchUse = entropy.dep.dep_getusedeps(atom)
except InvalidAtom:
matchUse = ()
atomSlot = entropy.dep.dep_getslot(atom)
matchRevision = entropy.dep.dep_get_entropy_revision(atom)
if isinstance(matchRevision, int):
if matchRevision < 0:
matchRevision = None
# use match
scan_atom = entropy.dep.remove_usedeps(atom)
# tag match
scan_atom = entropy.dep.remove_tag(scan_atom)
# slot match
scan_atom = entropy.dep.remove_slot(scan_atom)
if (matchSlot is None) and (atomSlot is not None):
matchSlot = atomSlot
# revision match
scan_atom = entropy.dep.remove_entropy_revision(scan_atom)
direction = ''
justname = True
pkgkey = ''
pkgname = ''
pkgcat = ''
pkgversion = ''
stripped_atom = ''
found_ids = []
default_package_ids = None
if scan_atom:
while True:
# check for direction
scan_cpv = entropy.dep.dep_getcpv(scan_atom)
stripped_atom = scan_cpv
if scan_atom.endswith("*"):
stripped_atom += "*"
direction = scan_atom[0:-len(stripped_atom)]
justname = entropy.dep.isjustname(scan_cpv)
pkgkey = stripped_atom
if justname == 0:
# get version
data = entropy.dep.catpkgsplit(scan_cpv)
if data is None:
break # badly formatted
wildcard = ""
if scan_atom.endswith("*"):
wildcard = "*"
pkgversion = data[2]+wildcard+"-"+data[3]
pkgkey = entropy.dep.dep_getkey(stripped_atom)
splitkey = pkgkey.split("/")
if (len(splitkey) == 2):
pkgcat, pkgname = splitkey
else:
pkgcat, pkgname = "null", splitkey[0]
break
# IDs found in the database that match our search
try:
found_ids, default_package_ids = self.__generate_found_ids_match(
pkgkey, pkgname, pkgcat, multiMatch)
except OperationalError:
# we are fault tolerant, cannot crash because
# tables are not available and validateDatabase()
# hasn't run
# found_ids = []
# default_package_ids = None
pass
### FILTERING
# filter slot and tag
if found_ids:
found_ids = self.__filterSlotTagUse(found_ids, matchSlot,
matchTag, matchUse, direction)
if maskFilter:
def _filter(pkg_id):
pkg_id, pkg_reason = self.maskFilter(pkg_id)
return pkg_id != -1
found_ids = set(filter(_filter, found_ids))
### END FILTERING
dbpkginfo = set()
if found_ids:
dbpkginfo = self.__handle_found_ids_match(found_ids, direction,
matchTag, matchRevision, justname, stripped_atom, pkgversion)
if not dbpkginfo:
if extendedResults:
if multiMatch:
x = set()
else:
x = (-1, 1, None, None, None,)
self.__atomMatchStoreCache(
atom, matchSlot,
multiMatch, maskFilter,
extendedResults, result = (x, 1)
)
return x, 1
else:
if multiMatch:
x = set()
else:
x = -1
self.__atomMatchStoreCache(
atom, matchSlot,
multiMatch, maskFilter,
extendedResults, result = (x, 1)
)
return x, 1
if multiMatch:
if extendedResults:
x = set([(x[0], 0, x[1], self.retrieveTag(x[0]), \
self.retrieveRevision(x[0])) for x in dbpkginfo])
self.__atomMatchStoreCache(
atom, matchSlot,
multiMatch, maskFilter,
extendedResults, result = (x, 0)
)
return x, 0
else:
x = set([x[0] for x in dbpkginfo])
self.__atomMatchStoreCache(
atom, matchSlot,
multiMatch, maskFilter,
extendedResults, result = (x, 0)
)
return x, 0
if len(dbpkginfo) == 1:
x = dbpkginfo.pop()
if extendedResults:
x = (x[0], 0, x[1], self.retrieveTag(x[0]),
self.retrieveRevision(x[0]),)
self.__atomMatchStoreCache(
atom, matchSlot,
multiMatch, maskFilter,
extendedResults, result = (x, 0)
)
return x, 0
else:
self.__atomMatchStoreCache(
atom, matchSlot,
multiMatch, maskFilter,
extendedResults, result = (x[0], 0)
)
return x[0], 0
# if a default_package_id is given by __generate_found_ids_match
# we need to respect it
# NOTE: this is only used by old-style virtual packages
# if (len(found_ids) > 1) and (default_package_id is not None):
# if default_package_id in found_ids:
# found_ids = set([default_package_id])
dbpkginfo = list(dbpkginfo)
if default_package_ids is not None:
# at this point, if default_package_ids is not None (== set())
# we must exclude all the package_ids not available in this list
# from dbpkginfo
dbpkginfo = [x for x in dbpkginfo if x[0] in default_package_ids]
pkgdata = {}
versions = set()
for x in dbpkginfo:
info_tuple = (x[1], self.retrieveTag(x[0]), \
self.retrieveRevision(x[0]))
versions.add(info_tuple)
pkgdata[info_tuple] = x[0]
# if matchTag is not specified, and tagged and non-tagged packages
# are available, prefer non-tagged ones, excluding others.
if not matchTag:
non_tagged_available = False
tagged_available = False
for ver, tag, rev in versions:
if tag:
tagged_available = True
else:
non_tagged_available = True
if tagged_available and non_tagged_available:
break
if tagged_available and non_tagged_available:
# filter out tagged
versions = set(((ver, tag, rev) for ver, tag, rev in versions \
if not tag))
newer = entropy.dep.get_entropy_newer_version(list(versions))[0]
x = pkgdata[newer]
if extendedResults:
x = (x, 0, newer[0], newer[1], newer[2])
self.__atomMatchStoreCache(
atom, matchSlot,
multiMatch, maskFilter,
extendedResults, result = (x, 0)
)
return x, 0
else:
self.__atomMatchStoreCache(
atom, matchSlot,
multiMatch, maskFilter,
extendedResults, result = (x, 0)
)
return x, 0
def __generate_found_ids_match(self, pkgkey, pkgname, pkgcat, multiMatch):
if pkgcat == "null":
results = self.searchName(pkgname, sensitive = True,
just_id = True)
else:
results = self.searchNameCategory(pkgname, pkgcat, just_id = True)
old_style_virtuals = None
# if it's a PROVIDE, search with searchProvide
# there's no package with that name
if (not results) and (pkgcat == self.VIRTUAL_META_PACKAGE_CATEGORY):
# look for default old-style virtual
virtuals = self.searchProvidedVirtualPackage(pkgkey)
if virtuals:
old_style_virtuals = set([x[0] for x in virtuals if x[1]])
flat_virtuals = [x[0] for x in virtuals]
if not old_style_virtuals:
old_style_virtuals = set(flat_virtuals)
results = flat_virtuals
if not results: # nothing found
del results
return set(), old_style_virtuals
if len(results) > 1: # need to choose
# if we are dealing with old-style virtuals, there is no need
# to go further and search stuff using category and name since
# we wouldn't find anything new
if old_style_virtuals is not None:
v_results = set()
for package_id in results:
virtual_cat, virtual_name = self.retrieveKeySplit(package_id)
v_result = self.searchNameCategory(
virtual_name, virtual_cat, just_id = True)
v_results.update(v_result)
del results
return set(v_results), old_style_virtuals
# if it's because category differs, it's a problem
found_cat = None
found_id = None
cats = set()
for package_id in results:
cat = self.retrieveCategory(package_id)
cats.add(cat)
if (cat == pkgcat) or \
((pkgcat == self.VIRTUAL_META_PACKAGE_CATEGORY) and \
(cat == pkgcat)):
# in case of virtual packages only
# (that they're not stored as provide)
found_cat = cat
# if we found something at least...
if (not found_cat) and (len(cats) == 1) and \
(pkgcat in (self.VIRTUAL_META_PACKAGE_CATEGORY, "null")):
found_cat = sorted(cats)[0]
if not found_cat:
# got the issue
del results
return set(), old_style_virtuals
# we can use found_cat
pkgcat = found_cat
# we need to search using the category
if (not multiMatch) and (pkgcat == "null"):
# we searched by name, we need to search using category
results = self.searchNameCategory(
pkgname, pkgcat, just_id = True)
# if we get here, we have found the needed IDs
return set(results), old_style_virtuals
###
### just found one result
###
package_id = set(results).pop()
# if pkgcat is virtual, it can be forced
if (pkgcat == self.VIRTUAL_META_PACKAGE_CATEGORY) and \
(old_style_virtuals is not None):
# in case of virtual packages only
# (that they're not stored as provide)
pkgcat, pkgname = self.retrieveKeySplit(package_id)
# check if category matches
if pkgcat != "null":
found_cat = self.retrieveCategory(package_id)
if pkgcat == found_cat:
return set([package_id]), old_style_virtuals
del results
return set(), old_style_virtuals # nope nope
# very good, here it is
del results
return set([package_id]), old_style_virtuals
def __handle_found_ids_match(self, found_ids, direction, matchTag,
matchRevision, justname, stripped_atom, pkgversion):
dbpkginfo = set()
# now we have to handle direction
if ((direction) or ((not direction) and (not justname)) or \
((not direction) and (not justname) \
and stripped_atom.endswith("*"))) and found_ids:
if (not justname) and \
((direction == "~") or (direction == "=") or \
((not direction) and (not justname)) or ((not direction) and \
not justname and stripped_atom.endswith("*"))):
# any revision within the version specified
# OR the specified version
if ((not direction) and (not justname)):
direction = "="
# remove gentoo revision (-r0 if none)
if (direction == "="):
if (pkgversion.split("-")[-1] == "r0"):
pkgversion = entropy.dep.remove_revision(
pkgversion)
if (direction == "~"):
pkgrevision = entropy.dep.dep_get_spm_revision(
pkgversion)
pkgversion = entropy.dep.remove_revision(pkgversion)
for package_id in found_ids:
dbver = self.retrieveVersion(package_id)
if (direction == "~"):
myrev = entropy.dep.dep_get_spm_revision(
dbver)
myver = entropy.dep.remove_revision(dbver)
if myver == pkgversion and pkgrevision <= myrev:
# found
dbpkginfo.add((package_id, dbver))
else:
# media-libs/test-1.2* support
if pkgversion[-1] == "*":
if dbver.startswith(pkgversion[:-1]):
dbpkginfo.add((package_id, dbver))
elif (matchRevision is not None) and (pkgversion == dbver):
dbrev = self.retrieveRevision(package_id)
if dbrev == matchRevision:
dbpkginfo.add((package_id, dbver))
elif (pkgversion == dbver) and (matchRevision is None):
dbpkginfo.add((package_id, dbver))
elif (direction.find(">") != -1) or (direction.find("<") != -1):
if not justname:
# remove revision (-r0 if none)
if pkgversion.endswith("r0"):
# remove
entropy.dep.remove_revision(pkgversion)
for package_id in found_ids:
revcmp = 0
tagcmp = 0
if matchRevision is not None:
dbrev = self.retrieveRevision(package_id)
revcmp = const_cmp(matchRevision, dbrev)
if matchTag is not None:
dbtag = self.retrieveTag(package_id)
tagcmp = const_cmp(matchTag, dbtag)
dbver = self.retrieveVersion(package_id)
pkgcmp = entropy.dep.compare_versions(
pkgversion, dbver)
if pkgcmp is None:
warnings.warn("WARNING, invalid version string " + \
"stored in %s: %s <-> %s" % (
self.name, pkgversion, dbver,)
)
continue
if direction == ">":
if pkgcmp < 0:
dbpkginfo.add((package_id, dbver))
elif (matchRevision is not None) and pkgcmp <= 0 \
and revcmp < 0:
dbpkginfo.add((package_id, dbver))
elif (matchTag is not None) and tagcmp < 0:
dbpkginfo.add((package_id, dbver))
elif direction == "<":
if pkgcmp > 0:
dbpkginfo.add((package_id, dbver))
elif (matchRevision is not None) and pkgcmp >= 0 \
and revcmp > 0:
dbpkginfo.add((package_id, dbver))
elif (matchTag is not None) and tagcmp > 0:
dbpkginfo.add((package_id, dbver))
elif direction == ">=":
if (matchRevision is not None) and pkgcmp <= 0:
if pkgcmp == 0:
if revcmp <= 0:
dbpkginfo.add((package_id, dbver))
else:
dbpkginfo.add((package_id, dbver))
elif pkgcmp <= 0 and matchRevision is None:
dbpkginfo.add((package_id, dbver))
elif (matchTag is not None) and tagcmp <= 0:
dbpkginfo.add((package_id, dbver))
elif direction == "<=":
if (matchRevision is not None) and pkgcmp >= 0:
if pkgcmp == 0:
if revcmp >= 0:
dbpkginfo.add((package_id, dbver))
else:
dbpkginfo.add((package_id, dbver))
elif pkgcmp >= 0 and matchRevision is None:
dbpkginfo.add((package_id, dbver))
elif (matchTag is not None) and tagcmp >= 0:
dbpkginfo.add((package_id, dbver))
else: # just the key
dbpkginfo = set([(x, self.retrieveVersion(x),) for x in found_ids])
return dbpkginfo
def __atomMatchFetchCache(self, *args):
if self._caching:
ck_sum = self.checksum(strict = False)
hash_str = self.__atomMatch_gen_hash_str(args)
cached = entropy.dump.loadobj("%s/%s/%s_%s" % (
self.__db_match_cache_key, self.name, ck_sum, hash_str,))
return cached
def __atomMatch_gen_hash_str(self, args):
data_str = repr(args)
sha1 = hashlib.sha1()
if sys.hexversion >= 0x3000000:
sha1.update(data_str.encode("utf-8"))
else:
sha1.update(data_str)
return sha1.hexdigest()
def __atomMatchStoreCache(self, *args, **kwargs):
if self._caching:
ck_sum = self.checksum(strict = False)
hash_str = self.__atomMatch_gen_hash_str(args)
self._cacher.push("%s/%s/%s_%s" % (
self.__db_match_cache_key, self.name, ck_sum, hash_str,),
kwargs.get('result'), async = False)
def __atomMatchValidateCache(self, cached_obj, multiMatch, extendedResults):
"""
This method validates the cache in order to avoid cache keys collissions
or corruption that could lead to improper data returned.
"""
# time wasted for a reason
data, rc = cached_obj
if multiMatch:
# data must be set !
if not isinstance(data, set):
return None
else:
# data must be int !
if not entropy.tools.isnumber(data):
return None
if rc != 0:
return cached_obj
if (not extendedResults) and (not multiMatch):
if not self.isPackageIdAvailable(data):
return None
elif extendedResults and (not multiMatch):
if not self.isPackageIdAvailable(data[0]):
return None
elif extendedResults and multiMatch:
package_ids = set([x[0] for x in data])
if not self.arePackageIdsAvailable(package_ids):
return None
elif (not extendedResults) and multiMatch:
# (set([x[0] for x in dbpkginfo]),0)
if not self.arePackageIdsAvailable(data):
return None
return cached_obj
def __filterSlot(self, package_id, slot):
if slot is None:
return package_id
dbslot = self.retrieveSlot(package_id)
if dbslot == slot:
return package_id
def __filterTag(self, package_id, tag, operators):
if tag is None:
return package_id
dbtag = self.retrieveTag(package_id)
compare = const_cmp(tag, dbtag)
# cannot do operator compare because it breaks the tag concept
if compare == 0:
return package_id
def __filterUse(self, package_id, uses):
if not uses:
return package_id
pkguse = set(self.retrieveUseflags(package_id))
enabled = set([x for x in uses if not x.startswith("-")])
disabled = set(uses) - enabled
# USE defaults support
enabled_use = set()
for use in enabled:
if use.endswith("(+)"):
use = use[:-3]
dis_use = "-" + use
if dis_use not in pkguse:
# consider it enabled by default if it's not
# disabled
pkguse.add(use)
elif use.endswith("(-)"):
# NOTE: this case should be filtered out by SPM
use = use[:-3]
if use not in pkguse:
# force disabled by default if it's not
# enabled
pkguse.add("-" + use)
enabled_use.add(use)
disabled_use = set()
for use in disabled:
# use starts with "-" here, example: -foo
if use.endswith("(+)"):
use = use[:-3]
# 3 cases here:
# 1 - use flag is not enabled (but available)
# do nothing. we want it not enabled anyway
# 2 - use flag is enabled (and available)
# do nothing, this will be caught later in the function
# 3 - use flag is not enabled (and also NOT available)
# since we cannot detect if a use flag is not available
# let's suppose that it won't be available and won't be
# added
# TODO: for case 3, we would need a new metadatum called
# "disabled_useflags"
en_use = use[1:]
if use not in pkguse:
pkguse.add(en_use)
elif use.endswith("(-)"):
# NOTE: this case should be filtered out by SPM
use = use[:-3]
en_use = use[1:]
# mark it as disabled if it's not available
if en_use not in pkguse:
pkguse.add(use)
else:
# for compatibility reasons with older Entropy versions,
# use flags not in pkguse are considered disabled.
pkguse.add(use)
disabled_use.add(use)
enabled_not_satisfied = enabled_use - pkguse
# check enabled
if enabled_not_satisfied:
return None
# check disabled
disabled_not_satisfied = disabled_use - pkguse
if disabled_not_satisfied:
return None
return package_id
def __filterSlotTagUse(self, found_ids, slot, tag, use, operators):
def myfilter(package_id):
package_id = self.__filterSlot(package_id, slot)
if not package_id:
return False
package_id = self.__filterUse(package_id, use)
if not package_id:
return False
package_id = self.__filterTag(package_id, tag, operators)
if not package_id:
return False
return True
return set(filter(myfilter, found_ids))