Files
entropy/libraries/entropy/db.py
T

8727 lines
302 KiB
Python

# -*- coding: utf-8 -*-
"""
@author: Fabio Erculiani <lxnay@sabayonlinux.org>
@contact: lxnay@sabayonlinux.org
@copyright: Fabio Erculiani
@license: GPL-2
B{Entropy Framework repository database module}.
Entropy repositories (server and client) are implemented as relational
databases. Currently, EntropyRepository class is the object that wraps
sqlite3 database queries and repository logic: there are no more
abstractions between the two because there is only one implementation
available at this time. In future, entropy.db will feature more backends
such as MySQL embedded, SparQL, remote repositories support via TCP socket,
etc. This will require a new layer between the repository interface now
offered by EntropyRepository and the underlying data retrieval logic.
Every repository interface available inherits from EntropyRepository
class and has to reimplement its own Schema subclass and its get_init
method (see EntropyRepository documentation for more information).
I{EntropyRepository} is the sqlite3 implementation of the repository
interface, as written above.
I{ServerRepositoryStatus} is a singleton containing the status of
server-side repositories. It is used to determine if repository has
been modified (tainted) or has been revision bumped already.
Revision bumps are automatic and happen on the very first data "commit".
Every repository features a revision number which is stored into the
"packages.db.revision" file. Only server-side (or community) repositories
are subject to this automation (revision file update on commit).
@todo: migrate to "_" (underscore) convention
"""
from __future__ import with_statement
import os
import shutil
from entropy.const import etpConst, etpCache
from entropy.exceptions import IncorrectParameter, InvalidAtom, \
SystemDatabaseError, OperationNotPermitted
from entropy.i18n import _
from entropy.output import brown, bold, red, blue, purple, darkred, darkgreen, \
TextInterface
from entropy.cache import EntropyCacher
from entropy.core import Singleton
from entropy.core.settings.base import SystemSettings
from entropy.spm.plugins.factory import get_default_instance as get_spm
try: # try with sqlite3 from >=python 2.5
from sqlite3 import dbapi2
except ImportError: # fallback to pysqlite
try:
from pysqlite2 import dbapi2
except ImportError, e:
raise SystemError(
"%s. %s: %s" % (
_("Entropy needs Python compiled with sqlite3 support"),
_("Error"),
e,
)
)
class ServerRepositoryStatus(Singleton):
"""
Server-side Repositories status information container.
"""
def init_singleton(self):
""" Singleton "constructor" """
self.__data = {}
self.__updates_log = {}
def __create_if_necessary(self, db):
if db not in self.__data:
self.__data[db] = {}
self.__data[db]['tainted'] = False
self.__data[db]['bumped'] = False
self.__data[db]['unlock_msg'] = False
def set_unlock_msg(self, db):
"""
Set bit which determines if the unlock warning has been already
printed to user.
@param db: database identifier
@type db: string
"""
self.__create_if_necessary(db)
self.__data[db]['unlock_msg'] = True
def unset_unlock_msg(self, db):
"""
Unset bit which determines if the unlock warning has been already
printed to user.
@param db: database identifier
@type db: string
"""
self.__create_if_necessary(db)
self.__data[db]['unlock_msg'] = False
def set_tainted(self, db):
"""
Set bit which determines if the repository which db points to has been
modified.
@param db: database identifier
@type db: string
"""
self.__create_if_necessary(db)
self.__data[db]['tainted'] = True
def unset_tainted(self, db):
"""
Unset bit which determines if the repository which db points to has been
modified.
@param db: database identifier
@type db: string
"""
self.__create_if_necessary(db)
self.__data[db]['tainted'] = False
def set_bumped(self, db):
"""
Set bit which determines if the repository which db points to has been
revision bumped.
@param db: database identifier
@type db: string
"""
self.__create_if_necessary(db)
self.__data[db]['bumped'] = True
def unset_bumped(self, db):
"""
Unset bit which determines if the repository which db points to has been
revision bumped.
@param db: database identifier
@type db: string
"""
self.__create_if_necessary(db)
self.__data[db]['bumped'] = False
def is_tainted(self, db):
"""
Return whether repository which db points to has been modified.
@param db: database identifier
@type db: string
"""
self.__create_if_necessary(db)
return self.__data[db]['tainted']
def is_bumped(self, db):
"""
Return whether repository which db points to has been revision bumped.
@param db: database identifier
@type db: string
"""
self.__create_if_necessary(db)
return self.__data[db]['bumped']
def is_unlock_msg(self, db):
"""
Return whether repository which db points to has outputed the unlock
warning message.
@param db: database identifier
@type db: string
"""
self.__create_if_necessary(db)
return self.__data[db]['unlock_msg']
def get_updates_log(self, db):
"""
Return dict() object containing metadata related to package
updates occured in a server-side repository.
"""
if db not in self.__updates_log:
self.__updates_log[db] = {}
return self.__updates_log[db]
class EntropyRepository:
"""
EntropyRepository implements SQLite3 based storage. In a Model-View based
pattern, it can be considered the "model".
Actually it's the only one available but more model backends will be
supported in future (which will inherit this class directly).
Every Entropy repository storage interface MUST inherit from this base
class.
@todo: refactoring and generalization needed
"""
SETTING_KEYS = [ "arch" ]
class Schema:
def get_init(self):
return """
CREATE TABLE baseinfo (
idpackage INTEGER PRIMARY KEY AUTOINCREMENT,
atom VARCHAR,
idcategory INTEGER,
name VARCHAR,
version VARCHAR,
versiontag VARCHAR,
revision INTEGER,
branch VARCHAR,
slot VARCHAR,
idlicense INTEGER,
etpapi INTEGER,
trigger INTEGER
);
CREATE TABLE extrainfo (
idpackage INTEGER PRIMARY KEY,
description VARCHAR,
homepage VARCHAR,
download VARCHAR,
size VARCHAR,
idflags INTEGER,
digest VARCHAR,
datecreation VARCHAR
);
CREATE TABLE content (
idpackage INTEGER,
file VARCHAR,
type VARCHAR
);
CREATE TABLE provide (
idpackage INTEGER,
atom VARCHAR
);
CREATE TABLE dependencies (
idpackage INTEGER,
iddependency INTEGER,
type INTEGER
);
CREATE TABLE dependenciesreference (
iddependency INTEGER PRIMARY KEY AUTOINCREMENT,
dependency VARCHAR
);
CREATE TABLE dependstable (
iddependency INTEGER PRIMARY KEY,
idpackage INTEGER
);
CREATE TABLE conflicts (
idpackage INTEGER,
conflict VARCHAR
);
CREATE TABLE mirrorlinks (
mirrorname VARCHAR,
mirrorlink VARCHAR
);
CREATE TABLE sources (
idpackage INTEGER,
idsource INTEGER
);
CREATE TABLE sourcesreference (
idsource INTEGER PRIMARY KEY AUTOINCREMENT,
source VARCHAR
);
CREATE TABLE useflags (
idpackage INTEGER,
idflag INTEGER
);
CREATE TABLE useflagsreference (
idflag INTEGER PRIMARY KEY AUTOINCREMENT,
flagname VARCHAR
);
CREATE TABLE keywords (
idpackage INTEGER,
idkeyword INTEGER
);
CREATE TABLE keywordsreference (
idkeyword INTEGER PRIMARY KEY AUTOINCREMENT,
keywordname VARCHAR
);
CREATE TABLE categories (
idcategory INTEGER PRIMARY KEY AUTOINCREMENT,
category VARCHAR
);
CREATE TABLE licenses (
idlicense INTEGER PRIMARY KEY AUTOINCREMENT,
license VARCHAR
);
CREATE TABLE flags (
idflags INTEGER PRIMARY KEY AUTOINCREMENT,
chost VARCHAR,
cflags VARCHAR,
cxxflags VARCHAR
);
CREATE TABLE configprotect (
idpackage INTEGER PRIMARY KEY,
idprotect INTEGER
);
CREATE TABLE configprotectmask (
idpackage INTEGER PRIMARY KEY,
idprotect INTEGER
);
CREATE TABLE configprotectreference (
idprotect INTEGER PRIMARY KEY AUTOINCREMENT,
protect VARCHAR
);
CREATE TABLE systempackages (
idpackage INTEGER PRIMARY KEY
);
CREATE TABLE injected (
idpackage INTEGER PRIMARY KEY
);
CREATE TABLE installedtable (
idpackage INTEGER PRIMARY KEY,
repositoryname VARCHAR,
source INTEGER
);
CREATE TABLE sizes (
idpackage INTEGER PRIMARY KEY,
size INTEGER
);
CREATE TABLE messages (
idpackage INTEGER,
message VARCHAR
);
CREATE TABLE counters (
counter INTEGER,
idpackage INTEGER,
branch VARCHAR,
PRIMARY KEY(idpackage,branch)
);
CREATE TABLE trashedcounters (
counter INTEGER
);
CREATE TABLE eclasses (
idpackage INTEGER,
idclass INTEGER
);
CREATE TABLE eclassesreference (
idclass INTEGER PRIMARY KEY AUTOINCREMENT,
classname VARCHAR
);
CREATE TABLE needed (
idpackage INTEGER,
idneeded INTEGER,
elfclass INTEGER
);
CREATE TABLE neededreference (
idneeded INTEGER PRIMARY KEY AUTOINCREMENT,
library VARCHAR
);
CREATE TABLE provided_libs (
idpackage INTEGER,
library VARCHAR,
path VARCHAR,
elfclass INTEGER
);
CREATE TABLE treeupdates (
repository VARCHAR PRIMARY KEY,
digest VARCHAR
);
CREATE TABLE treeupdatesactions (
idupdate INTEGER PRIMARY KEY AUTOINCREMENT,
repository VARCHAR,
command VARCHAR,
branch VARCHAR,
date VARCHAR
);
CREATE TABLE licensedata (
licensename VARCHAR UNIQUE,
text BLOB,
compressed INTEGER
);
CREATE TABLE licenses_accepted (
licensename VARCHAR UNIQUE
);
CREATE TABLE triggers (
idpackage INTEGER PRIMARY KEY,
data BLOB
);
CREATE TABLE entropy_misc_counters (
idtype INTEGER PRIMARY KEY,
counter INTEGER
);
CREATE TABLE categoriesdescription (
category VARCHAR,
locale VARCHAR,
description VARCHAR
);
CREATE TABLE packagesets (
setname VARCHAR,
dependency VARCHAR
);
CREATE TABLE packagechangelogs (
category VARCHAR,
name VARCHAR,
changelog BLOB,
PRIMARY KEY (category, name)
);
CREATE TABLE automergefiles (
idpackage INTEGER,
configfile VARCHAR,
md5 VARCHAR
);
CREATE TABLE packagesignatures (
idpackage INTEGER PRIMARY KEY,
sha1 VARCHAR,
sha256 VARCHAR,
sha512 VARCHAR
);
CREATE TABLE packagespmphases (
idpackage INTEGER PRIMARY KEY,
phases VARCHAR
);
CREATE TABLE entropy_branch_migration (
repository VARCHAR,
from_branch VARCHAR,
to_branch VARCHAR,
post_migration_md5sum VARCHAR,
post_upgrade_md5sum VARCHAR,
PRIMARY KEY (repository, from_branch, to_branch)
);
CREATE TABLE xpakdata (
idpackage INTEGER PRIMARY KEY,
data BLOB
);
CREATE TABLE settings (
setting_name VARCHAR,
setting_value VARCHAR,
PRIMARY KEY(setting_name)
);
"""
import entropy.tools as entropyTools
import entropy.dump as dumpTools
import threading
def __init__(self, readOnly = False, noUpload = False, dbFile = None,
clientDatabase = False, xcache = False, dbname = etpConst['serverdbid'],
indexing = True, OutputInterface = None, skipChecks = False,
useBranch = None, lockRemote = True):
"""
EntropyRepository constructor.
@keyword readOnly: open file in read-only mode
@type readOnly: bool
@keyword noUpload: server-side setting for not allowing database
uploads when remote revision is lower than local
@type noUpload: bool
@keyword dbFile: path to database to open
@type dbFile: string
@keyword clientDatabase: state that EntropyRepository instance is
a client-side one
@type clientDatabase: bool
@keyword xcache: enable on-disk cache
@type xcache: bool
@keyword dbname: EntropyRepository instance identifier
@type dbname: string
@keyword indexing: enable database indexes
@type indexing: bool
@keyword OutputInterface: interface used to communicate with the user.
must inherit entropy.output.TextInterface
@type OutputInterface: entropy.output.TextInterface based instance
@keyword skipChecks: if True, skip integrity checks
@type skipChecks: bool
@keyword useBranch: if True, it won't use SystemSettings' branch
setting but rather the one provided
@type useBranch: string
@keyword lockRemote: determine whether remote server-side database
should be locked when updating the local version
@type lockRemote: bool
"""
self.SystemSettings = SystemSettings()
self.srv_sys_settings_plugin = \
etpConst['system_settings_plugins_ids']['server_plugin']
self.dbMatchCacheKey = etpCache['dbMatch']
self.client_settings_plugin_id = etpConst['system_settings_plugins_ids']['client_plugin']
self.db_branch = self.SystemSettings['repositories']['branch']
self.Cacher = EntropyCacher()
self.dbname = dbname
self.lockRemote = lockRemote
if self.dbname == etpConst['clientdbid']:
self.db_branch = None
if useBranch != None:
self.db_branch = useBranch
if OutputInterface is None:
OutputInterface = TextInterface()
if dbFile is None:
raise IncorrectParameter("IncorrectParameter: %s" % (
_("valid database path needed"),) )
self.__write_mutex = self.threading.RLock()
self.dbapi2 = dbapi2
# setup output interface
self.OutputInterface = OutputInterface
self.updateProgress = self.OutputInterface.updateProgress
self.askQuestion = self.OutputInterface.askQuestion
# setup service interface
self.readOnly = readOnly
self.noUpload = noUpload
self.clientDatabase = clientDatabase
self.xcache = xcache
self.indexing = indexing
self.skipChecks = skipChecks
if not self.skipChecks:
if not self.entropyTools.is_user_in_entropy_group():
# forcing since we won't have write access to db
self.indexing = False
# live systems don't like wasting RAM
if self.entropyTools.islive():
self.indexing = False
self.dbFile = dbFile
self.dbclosed = True
self.server_repo = None
if not self.clientDatabase:
self.server_repo = self.dbname[len(etpConst['serverdbid']):]
self._create_dbstatus_data()
if not self.skipChecks:
# no caching for non root and server connections
if (self.dbname.startswith(etpConst['serverdbid'])) or \
(not self.entropyTools.is_user_in_entropy_group()):
self.xcache = False
self.live_cache = {}
# create connection
self.connection = self.dbapi2.connect(dbFile, timeout=300.0,
check_same_thread = False)
self.cursor = self.connection.cursor()
if not self.skipChecks:
try:
if os.access(self.dbFile, os.W_OK) and \
self._doesTableExist('baseinfo') and \
self._doesTableExist('extrainfo'):
if self.entropyTools.islive(): # this works
if etpConst['systemroot']:
self._databaseStructureUpdates()
else:
self._databaseStructureUpdates()
except self.dbapi2.Error:
self.cursor.close()
self.connection.close()
raise
# now we can set this to False
self.dbclosed = False
def setCacheSize(self, size):
"""
Change low-level, storage engine based cache size.
@param size: new size
@type size: int
"""
self.cursor.execute('PRAGMA cache_size = '+str(size))
def setDefaultCacheSize(self, size):
"""
Change default low-level, storage engine based cache size.
@param size: new default size
@type size: int
"""
self.cursor.execute('PRAGMA default_cache_size = '+str(size))
def __del__(self):
if not self.dbclosed:
self.closeDB()
def _create_dbstatus_data(self):
"""
Server-side function that setups server status information
"""
from entropy.server.interfaces import Server
srv = Server()
taint_file = srv.get_local_database_taint_file(self.server_repo)
if os.path.isfile(taint_file):
dbs = ServerRepositoryStatus()
dbs.set_tainted(self.dbFile)
dbs.set_bumped(self.dbFile)
def closeDB(self):
"""
Close repository storage communication.
Note: once issues this, you won't be able to use such instance
anymore.
"""
self.dbclosed = True
# if the class is opened readOnly, close and forget
if self.readOnly:
self.cursor.close()
self.connection.close()
return
if self.clientDatabase:
self.commitChanges()
self.cursor.close()
self.connection.close()
return
sts = ServerRepositoryStatus()
if not sts.is_tainted(self.dbFile):
# we can unlock it, no changes were made
from entropy.server.interfaces import Server
srv = Server()
srv.MirrorsService.lock_mirrors(False, repo = self.server_repo)
elif not sts.is_unlock_msg(self.dbFile):
u_msg = _("Mirrors have not been unlocked. Remember to sync them.")
self.updateProgress(
darkgreen(u_msg),
importance = 1,
type = "info",
header = brown(" * ")
)
sts.set_unlock_msg(self.dbFile) # avoid spamming
self.commitChanges()
self.cursor.close()
self.connection.close()
def vacuum(self):
"""
Repository storage cleanup and optimization function.
"""
self.cursor.execute("vacuum")
def commitChanges(self, force = False):
"""
Commit actual changes and make them permanently stored.
@keyword force: force commit, despite read-only bit being set
@type force: bool
"""
if self.readOnly and not force:
return
try:
self.connection.commit()
except self.dbapi2.Error:
pass
if not self.clientDatabase:
self.taintDatabase()
dbs = ServerRepositoryStatus()
if (dbs.is_tainted(self.dbFile)) and \
(not dbs.is_bumped(self.dbFile)):
# bump revision, setting DatabaseBump causes
# the session to just bump once
dbs.set_bumped(self.dbFile)
self._revisionBump()
def taintDatabase(self):
"""
Server-side method that render your repository storage tainted,
modified.
"""
# if it's equo to open it, this should be avoided
from entropy.server.interfaces import Server
srv = Server()
# taint the database status
taint_file = srv.get_local_database_taint_file(repo = self.server_repo)
f = open(taint_file, "w")
f.write(etpConst['currentarch']+" database tainted\n")
f.flush()
f.close()
ServerRepositoryStatus().set_tainted(self.dbFile)
def untaintDatabase(self):
"""
Server-side method that render your repository storage NOT tainted,
modified.
"""
# if it's equo to open it, this should be avoided
from entropy.server.interfaces import Server
srv = Server()
ServerRepositoryStatus().unset_tainted(self.dbFile)
# untaint the database status
taint_file = srv.get_local_database_taint_file(repo = self.server_repo)
if os.path.isfile(taint_file):
os.remove(taint_file)
def _revisionBump(self):
"""
Entropy repository revision bumping function. Every time it's called,
revision is incremented by 1.
"""
from entropy.server.interfaces import Server
srv = Server()
revision_file = srv.get_local_database_revision_file(
repo = self.server_repo)
if not os.path.isfile(revision_file):
revision = 1
else:
f = open(revision_file, "r")
revision = int(f.readline().strip())
revision += 1
f.close()
f = open(revision_file, "w")
f.write(str(revision)+"\n")
f.flush()
f.close()
def isDatabaseTainted(self):
"""
Server-side function used to determine whether repository database
has been modified.
@return: taint status
@rtype: bool
"""
from entropy.server.interfaces import Server
srv = Server()
taint_file = srv.get_local_database_taint_file(repo = self.server_repo)
if os.path.isfile(taint_file):
return True
return False
def initializeDatabase(self):
"""
WARNING: it will erase your database.
This method (re)initialize the repository, dropping all its content.
"""
my = self.Schema()
for table in self.listAllTables():
try:
self.cursor.execute("DROP TABLE %s" % (table,))
except self.dbapi2.OperationalError:
# skip tables that can't be dropped
continue
self.cursor.executescript(my.get_init())
self._databaseStructureUpdates()
# set cache size
self.setCacheSize(8192)
self.setDefaultCacheSize(8192)
self._setupInitialSettings()
self.commitChanges()
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 = self.entropyTools.dep_getkey(atom)
category = atom_key.split("/")[0]
matches, sm_rc = self.atomMatch(atom, matchSlot = from_slot,
multiMatch = True)
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 idpackage in matches:
myslot = self.retrieveSlot(idpackage)
mycategory = self.retrieveCategory(idpackage)
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 self.entropyTools.dep_getkey(x) == atom_key]
if dep_atoms:
new_actions.append(action)
elif doaction[0] == "move":
atom = doaction[1] # usually a key
atom_key = self.entropyTools.dep_getkey(atom)
category = atom_key.split("/")[0]
matches, m_rc = self.atomMatch(atom, multiMatch = True)
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 idpackage in matches:
mycategory = self.retrieveCategory(idpackage)
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 \
self.entropyTools.dep_getkey(x) == atom_key]
if dep_atoms:
new_actions.append(action)
return new_actions
def runTreeUpdatesActions(self, actions):
# this is the place to add extra actions support
"""
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 fixpackages")),
red(_("it could take a while")),
)
self.updateProgress(
mytxt,
importance = 1,
type = "warning",
header = darkred(" * ")
)
try:
spm = get_spm(self)
spm.packages_repositories_metadata_update()
except:
self.entropyTools.print_traceback()
pass
spm_moves = set()
quickpkg_atoms = set()
for action in actions:
command = action.split()
mytxt = "%s: %s: %s." % (
bold(_("ENTROPY")),
red(_("action")),
blue(action),
)
self.updateProgress(
mytxt,
importance = 1,
type = "warning",
header = darkred(" * ")
)
if command[0] == "move":
spm_moves.add(action)
quickpkg_atoms |= self.runTreeUpdatesMoveAction(command[1:],
quickpkg_atoms)
elif command[0] == "slotmove":
quickpkg_atoms |= self.runTreeUpdatesSlotmoveAction(command[1:],
quickpkg_atoms)
mytxt = "%s: %s." % (
bold(_("ENTROPY")),
blue(_("package move actions complete")),
)
self.updateProgress(
mytxt,
importance = 1,
type = "info",
header = purple(" @@ ")
)
if spm_moves:
try:
self.doTreeupdatesSpmCleanup(spm_moves)
except Exception, e:
mytxt = "%s: %s: %s, %s." % (
bold(_("WARNING")),
red(_("Cannot run SPM cleanup, error")),
Exception,
e,
)
self.entropyTools.print_traceback()
mytxt = "%s: %s." % (
bold(_("ENTROPY")),
blue(_("package moves completed successfully")),
)
self.updateProgress(
mytxt,
importance = 1,
type = "info",
header = brown(" @@ ")
)
# discard cache
self.clearCache()
return quickpkg_atoms
def runTreeUpdatesMoveAction(self, move_command, quickpkg_queue):
# -- 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 quickpkg() to build the new binary and
# tainted binaries owning tainted iddependency and taint database
"""
Method not suited for general purpose usage.
Executes package name move action passed.
@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 = self.entropyTools.dep_getkey(dep_from)
key_to = move_command[1]
cat_to = key_to.split("/")[0]
name_to = key_to.split("/")[1]
matches = self.atomMatch(dep_from, multiMatch = True)
iddependencies = set()
for idpackage in matches[0]:
slot = self.retrieveSlot(idpackage)
old_atom = self.retrieveAtom(idpackage)
new_atom = old_atom.replace(key_from, key_to)
### UPDATE DATABASE
# update category
self.setCategory(idpackage, cat_to)
# update name
self.setName(idpackage, name_to)
# update atom
self.setAtom(idpackage, new_atom)
# look for packages we need to quickpkg again
# note: quickpkg_queue is simply ignored if self.clientDatabase
quickpkg_queue.add(key_to+":"+str(slot))
if not self.clientDatabase:
# check for injection and warn the developer
injected = self.isInjected(idpackage)
if injected:
mytxt = "%s: %s %s. %s !!! %s." % (
bold(_("INJECT")),
blue(str(new_atom)),
red(_("has been injected")),
red(_("quickpkg manually to update embedded db")),
red(_("Repository database updated anyway")),
)
self.updateProgress(
mytxt,
importance = 1,
type = "warning",
header = darkred(" * ")
)
iddeps = self.searchDependency(key_from, like = True, multi = True)
for iddep in iddeps:
# update string
mydep = self.getDependency(iddep)
mydep_key = self.entropyTools.dep_getkey(mydep)
# avoid changing wrong atoms -> dev-python/qscintilla-python would
# become x11-libs/qscintilla if we don't do this check
if mydep_key != key_from:
continue
mydep = mydep.replace(key_from, key_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.searchIdpackageFromIddependency(iddep)
self.commitChanges()
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 idpackage_owner in iddependencies:
myatom = self.retrieveAtom(idpackage_owner)
myatom = myatom.replace(key_from, key_to)
quickpkg_queue.add(myatom)
return quickpkg_queue
def runTreeUpdatesSlotmoveAction(self, slotmove_command, quickpkg_queue):
# -- 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 quickpkg() to build the new
# binary and tainted binaries owning tainted iddependency
# and taint database
"""
Method not suited for general purpose usage.
Executes package slot move action passed.
@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 = self.entropyTools.dep_getkey(atom)
slot_from = slotmove_command[1]
slot_to = slotmove_command[2]
matches = self.atomMatch(atom, multiMatch = True)
iddependencies = set()
matched_idpackages = matches[0]
for idpackage in matched_idpackages:
### UPDATE DATABASE
# update slot
self.setSlot(idpackage, slot_to)
# look for packages we need to quickpkg again
# note: quickpkg_queue is simply ignored if self.clientDatabase
quickpkg_queue.add(atom+":"+str(slot_to))
if not self.clientDatabase:
# check for injection and warn the developer
injected = self.isInjected(idpackage)
if injected:
mytxt = "%s: %s %s. %s !!! %s." % (
bold(_("INJECT")),
blue(str(atom)),
red(_("has been injected")),
red(_("quickpkg manually to update embedded db")),
red(_("Repository database updated anyway")),
)
self.updateProgress(
mytxt,
importance = 1,
type = "warning",
header = darkred(" * ")
)
# 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)
mydep_key = self.entropyTools.dep_getkey(mydep)
if mydep_key != atomkey:
continue
if not mydep.endswith(":"+slot_from): # probably slotted dep
continue
mydep_match = self.atomMatch(mydep)
if mydep_match not in matched_idpackages:
continue
mydep = mydep.replace(":"+slot_from, ":"+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.searchIdpackageFromIddependency(iddep)
self.commitChanges()
for idpackage_owner in iddependencies:
myatom = self.retrieveAtom(idpackage_owner)
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 = self.entropyTools.dep_getkey(key)
try:
spm = get_spm(self)
except:
self.entropyTools.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 \
self.entropyTools.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.updateProgress(
mytxt,
importance = 1,
type = "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 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': u'zzz',
'sha1': u'zzz',
'sha512': u'zzz'
},
'slot': u'0',
'datecreation': u'1247681752.93',
'description': u'Standard (de)compression library',
'useflags': set([u'kernel_linux']),
'eclasses': set([u'multilib']),
'config_protect_mask': u'string string', 'etpapi': 3,
'mirrorlinks': [],
'cxxflags': u'-Os -march=x86-64 -pipe',
'injected': False,
'licensedata': {u'ZLIB': u"lictext"},
'dependencies': {},
'chost': u'x86_64-pc-linux-gnu',
'config_protect': u'string string',
'download': u'packages/amd64/4/sys-libs:zlib-1.2.3-r1.tbz2',
'conflicts': set([]),
'digest': u'fd54248ae060c287b1ec939de3e55332',
'size': u'136302',
'category': u'sys-libs',
'license': u'ZLIB',
'sources': set(),
'name': u'zlib',
'versiontag': u'',
'changelog': u"text",
'provide': set([]),
'trigger': u'text',
'counter': 22331,
'messages': [],
'branch': u'4',
'content': {},
'needed': [(u'libc.so.6', 2)],
'version': u'1.2.3-r1',
'keywords': set(),
'cflags': u'-Os -march=x86-64 -pipe',
'disksize': 932206, 'spm_phases': None,
'homepage': u'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: tuple composed by
- idpackage: unique Entropy Repository package identifier
- revision: final package revision selected
- pkg_data: new Entropy package metadata dict
@rtype: tuple
"""
def remove_conflicting_packages(pkgdata):
manual_deps = set()
removelist = self.retrieve_packages_to_remove(
pkgdata['name'], pkgdata['category'],
pkgdata['slot'], pkgdata['injected']
)
for r_idpackage in removelist:
manual_deps |= self.retrieveManualDependencies(r_idpackage)
self.removePackage(r_idpackage, do_cleanup = False,
do_commit = False)
# inject old manual dependencies back to package metadata
for manual_dep in manual_deps:
if manual_dep in pkgdata['dependencies']:
continue
pkgdata['dependencies'][manual_dep] = etpConst['spm']['mdepend_id']
if self.clientDatabase:
remove_conflicting_packages(pkg_data)
return self.addPackage(pkg_data, revision = forcedRevision,
formatted_content = formattedContent)
# build atom string, server side
pkgatom = self.entropyTools.create_package_atom_string(
pkg_data['category'], pkg_data['name'], pkg_data['version'],
pkg_data['versiontag'])
foundid = self.isAtomAvailable(pkgatom)
if foundid < 0: # same atom doesn't exist in any branch
remove_conflicting_packages(pkg_data)
return self.addPackage(pkg_data, revision = forcedRevision,
formatted_content = formattedContent)
idpackage = self.getIDPackage(pkgatom)
curRevision = forcedRevision
if forcedRevision == -1:
curRevision = 0
if idpackage != -1:
curRevision = self.retrieveRevision(idpackage)
# remove old package atom, we do it here because othersie
if idpackage != -1:
# injected packages wouldn't be removed by addPackages
self.removePackage(idpackage)
if forcedRevision == -1:
curRevision += 1
# add the new one
remove_conflicting_packages(pkg_data)
return self.addPackage(pkg_data, revision = curRevision,
formatted_content = formattedContent)
def retrieve_packages_to_remove(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 (idpackages)
@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
# 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']
if not self.clientDatabase: # server-side db
srv_plug_settings = self.SystemSettings.get(srv_ss_plg)
if srv_plug_settings != None:
if srv_plug_settings['server']['exp_based_scope']:
# in case support is enabled, return an empty set
filter_similar = True
searchsimilar = self.searchPackagesByNameAndCategory(
name = name,
category = category,
sensitive = True
)
if filter_similar:
# filter out packages in the same scope that are allowed to stay
idpkgs = self.SystemSettings[srv_ss_fs_plg]['repos'].get(
self.server_repo)
if idpkgs:
if -1 in idpkgs:
del searchsimilar[:]
else:
searchsimilar = [x for x in searchsimilar if x[1] \
not in idpkgs]
for atom, idpackage in searchsimilar:
# get the package slot
myslot = self.retrieveSlot(idpackage)
# we merely ignore packages with
# negative counters, since they're the injected ones
if self.isInjected(idpackage): continue
if slot == myslot:
# remove!
removelist.add(idpackage)
return removelist
def addPackage(self, pkg_data, revision = -1, idpackage = 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()}.
@param pkg_data: Entropy package metadata
@type pkg_data: dict
@keyword revision: force a specific Entropy package revision
@type revision: int
@keyword idpackage: 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 idpackage: 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: tuple composed by
- idpackage: unique Entropy Repository package identifier
- revision: final package revision selected
- pkg_data: new Entropy package metadata dict
@rtype: tuple
"""
if revision == -1:
try:
revision = int(pkg_data['revision'])
except (KeyError, ValueError):
pkg_data['revision'] = 0 # revision not specified
revision = 0
elif not pkg_data.has_key('revision'):
pkg_data['revision'] = revision
# create new category if it doesn't exist
catid = self.isCategoryAvailable(pkg_data['category'])
if catid == -1:
catid = self.addCategory(pkg_data['category'])
# create new license if it doesn't exist
licid = self.isLicenseAvailable(pkg_data['license'])
if licid == -1:
licid = self.addLicense(pkg_data['license'])
idprotect = self.isProtectAvailable(pkg_data['config_protect'])
if idprotect == -1:
idprotect = self.addProtect(pkg_data['config_protect'])
idprotect_mask = self.isProtectAvailable(
pkg_data['config_protect_mask'])
if idprotect_mask == -1:
idprotect_mask = self.addProtect(pkg_data['config_protect_mask'])
idflags = self.areCompileFlagsAvailable(pkg_data['chost'],
pkg_data['cflags'], pkg_data['cxxflags'])
if idflags == -1:
idflags = self.addCompileFlags(pkg_data['chost'],
pkg_data['cflags'], pkg_data['cxxflags'])
trigger = 0
if pkg_data['trigger']:
trigger = 1
# baseinfo
pkgatom = self.entropyTools.create_package_atom_string(
pkg_data['category'], pkg_data['name'], pkg_data['version'],
pkg_data['versiontag'])
# add atom metadatum
pkg_data['atom'] = pkgatom
mybaseinfo_data = (pkgatom, catid, pkg_data['name'],
pkg_data['version'], pkg_data['versiontag'], revision,
pkg_data['branch'], pkg_data['slot'],
licid, pkg_data['etpapi'], trigger,
)
myidpackage_string = 'NULL'
if isinstance(idpackage, (int, long,)):
manual_deps = self.retrieveManualDependencies(idpackage)
# does it exist?
self.removePackage(idpackage, do_cleanup = False,
do_commit = False, do_rss = False)
myidpackage_string = '?'
mybaseinfo_data = (idpackage,)+mybaseinfo_data
# merge old manual dependencies
dep_dict = pkg_data['dependencies']
for manual_dep in manual_deps:
if manual_dep in dep_dict:
continue
dep_dict[manual_dep] = etpConst['spm']['mdepend_id']
else:
# force to None
idpackage = None
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT INTO baseinfo VALUES (%s,?,?,?,?,?,?,?,?,?,?,?)""" % (
myidpackage_string,), mybaseinfo_data)
if idpackage is None:
idpackage = cur.lastrowid
# extrainfo
self.cursor.execute(
'INSERT INTO extrainfo VALUES (?,?,?,?,?,?,?,?)',
( idpackage,
pkg_data['description'],
pkg_data['homepage'],
pkg_data['download'],
pkg_data['size'],
idflags,
pkg_data['digest'],
pkg_data['datecreation'],
)
)
### other information iserted below are not as
### critical as these above
# tables using a select
self.insertEclasses(idpackage, pkg_data['eclasses'])
self.insertNeeded(idpackage, pkg_data['needed'])
self.insertDependencies(idpackage, pkg_data['dependencies'])
self.insertSources(idpackage, pkg_data['sources'])
self.insertUseflags(idpackage, pkg_data['useflags'])
self.insertKeywords(idpackage, pkg_data['keywords'])
self.insertLicenses(pkg_data['licensedata'])
self.insertMirrors(pkg_data['mirrorlinks'])
# package ChangeLog
if pkg_data.get('changelog'):
self.insertChangelog(pkg_data['category'], pkg_data['name'],
pkg_data['changelog'])
# package signatures
if pkg_data.get('signatures'):
signatures = pkg_data['signatures']
sha1, sha256, sha512 = signatures['sha1'], \
signatures['sha256'], signatures['sha512']
self.insertSignatures(idpackage, sha1, sha256, sha512)
if pkg_data.get('provided_libs'):
self.insertProvidedLibraries(idpackage, pkg_data['provided_libs'])
# spm phases
if pkg_data.get('spm_phases') != None:
self.insertSpmPhases(idpackage, pkg_data['spm_phases'])
# not depending on other tables == no select done
self.insertContent(idpackage, pkg_data['content'],
already_formatted = formatted_content)
# handle SPM UID<->idpackage binding
pkg_data['counter'] = int(pkg_data['counter'])
if not pkg_data['injected'] and (pkg_data['counter'] != -1):
pkg_data['counter'] = self.bindSpmPackageUid(
idpackage, pkg_data['counter'], pkg_data['branch'])
self.insertOnDiskSize(idpackage, pkg_data['disksize'])
if pkg_data['trigger']:
self.insertTrigger(idpackage, pkg_data['trigger'])
self.insertConflicts(idpackage, pkg_data['conflicts'])
self.insertProvide(idpackage, pkg_data['provide'])
self.insertMessages(idpackage, pkg_data['messages'])
self.insertConfigProtect(idpackage, idprotect)
self.insertConfigProtect(idpackage, idprotect_mask, mask = True)
# injected?
if pkg_data.get('injected'):
self.setInjected(idpackage, do_commit = False)
# is it a system package?
if pkg_data.get('systempackage'):
self.setSystemPackage(idpackage, do_commit = False)
self.clearCache() # we do live_cache.clear() here too
if do_commit:
self.commitChanges()
### RSS Atom support
### dictionary will be elaborated by activator
if self.SystemSettings.has_key(self.srv_sys_settings_plugin):
srv_data = self.SystemSettings[self.srv_sys_settings_plugin]
if srv_data['server']['rss']['enabled'] and not self.clientDatabase:
self._write_rss_for_added_package(pkgatom, revision,
pkg_data['description'], pkg_data['homepage'])
# Update category description
if not self.clientDatabase:
mycategory = pkg_data['category']
descdata = {}
try:
descdata = self._get_category_description_from_disk(mycategory)
except (IOError, OSError, EOFError,):
pass
if descdata:
self.setCategoryDescription(mycategory, descdata)
return idpackage, revision, pkg_data
def _write_rss_for_added_package(self, pkgatom, revision, description,
homepage):
# setup variables we're going to use
srv_repo = self.server_repo
rss_atom = "%s~%s" % (pkgatom, revision,)
status = ServerRepositoryStatus()
srv_updates = status.get_updates_log(srv_repo)
rss_name = srv_repo + etpConst['rss-dump-name']
# load metadata from on disk cache, if available
rss_obj = self.dumpTools.loadobj(rss_name)
if rss_obj:
srv_updates.update(rss_obj)
# setup metadata keys, if not available
if not srv_updates.has_key('added'):
srv_updates['added'] = {}
if not srv_updates.has_key('removed'):
srv_updates['removed'] = {}
if not srv_updates.has_key('light'):
srv_updates['light'] = {}
# if pkgatom (rss_atom) is in the "removed" metadata, drop it
if rss_atom in srv_updates['removed']:
del srv_updates['removed'][rss_atom]
# add metadata
srv_updates['added'][rss_atom] = {}
srv_updates['added'][rss_atom]['description'] = description
srv_updates['added'][rss_atom]['homepage'] = homepage
srv_updates['light'][rss_atom] = {}
srv_updates['light'][rss_atom]['description'] = description
# save to disk
self.dumpTools.dumpobj(rss_name, srv_updates)
def _write_rss_for_removed_package(self, idpackage):
"""
docstring_title
@param idpackage: package indentifier
@type idpackage: int
@return:
@rtype:
"""
# setup variables we're going to use
srv_repo = self.server_repo
rss_revision = self.retrieveRevision(idpackage)
rss_atom = "%s~%s" % (self.retrieveAtom(idpackage), rss_revision,)
status = ServerRepositoryStatus()
srv_updates = status.get_updates_log(srv_repo)
rss_name = srv_repo + etpConst['rss-dump-name']
# load metadata from on disk cache, if available
rss_obj = self.dumpTools.loadobj(rss_name)
if rss_obj:
srv_updates.update(rss_obj)
# setup metadata keys, if not available
if not srv_updates.has_key('added'):
srv_updates['added'] = {}
if not srv_updates.has_key('removed'):
srv_updates['removed'] = {}
if not srv_updates.has_key('light'):
srv_updates['light'] = {}
# if pkgatom (rss_atom) is in the "added" metadata, drop it
if rss_atom in srv_updates['added']:
del srv_updates['added'][rss_atom]
# same thing for light key
if rss_atom in srv_updates['light']:
del srv_updates['light'][rss_atom]
# add metadata
mydict = {}
try:
mydict['description'] = self.retrieveDescription(idpackage)
except TypeError:
mydict['description'] = "N/A"
try:
mydict['homepage'] = self.retrieveHomepage(idpackage)
except TypeError:
mydict['homepage'] = ""
srv_updates['removed'][rss_atom] = mydict
# save to disk
self.dumpTools.dumpobj(rss_name, srv_updates)
def removePackage(self, idpackage, do_cleanup = True, do_commit = True,
do_rss = True):
"""
Remove package from this Entropy repository using it's identifier
(idpackage).
@param idpackage: Entropy repository package indentifier
@type idpackage: 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 do_rss: triggered only for server-side repositories, if True,
generates information about the removal in RSS form, dumping data
to cache (used internally to handle RSS support for repositories).
@type do_rss: bool
"""
# clear caches
self.clearCache()
### RSS Atom support
### dictionary will be elaborated by activator
if self.SystemSettings.has_key(self.srv_sys_settings_plugin):
if self.SystemSettings[self.srv_sys_settings_plugin]['server']['rss']['enabled'] and \
(not self.clientDatabase) and do_rss:
# store addPackage action
self._write_rss_for_removed_package(idpackage)
with self.__write_mutex:
r_tup = (idpackage,)*20
self.cursor.executescript("""
DELETE FROM baseinfo WHERE idpackage = %d;
DELETE FROM extrainfo WHERE idpackage = %d;
DELETE FROM dependencies WHERE idpackage = %d;
DELETE FROM provide WHERE idpackage = %d;
DELETE FROM conflicts WHERE idpackage = %d;
DELETE FROM configprotect WHERE idpackage = %d;
DELETE FROM configprotectmask WHERE idpackage = %d;
DELETE FROM sources WHERE idpackage = %d;
DELETE FROM useflags WHERE idpackage = %d;
DELETE FROM keywords WHERE idpackage = %d;
DELETE FROM content WHERE idpackage = %d;
DELETE FROM messages WHERE idpackage = %d;
DELETE FROM counters WHERE idpackage = %d;
DELETE FROM sizes WHERE idpackage = %d;
DELETE FROM eclasses WHERE idpackage = %d;
DELETE FROM needed WHERE idpackage = %d;
DELETE FROM triggers WHERE idpackage = %d;
DELETE FROM systempackages WHERE idpackage = %d;
DELETE FROM injected WHERE idpackage = %d;
DELETE FROM installedtable WHERE idpackage = %d;
""" % r_tup)
# FIXME: move these inside the main SQL script above
try:
self.removeAutomergefiles(idpackage)
except self.dbapi2.OperationalError:
pass
try:
self.removeSignatures(idpackage)
except self.dbapi2.OperationalError:
pass
try:
self.removeSpmPhases(idpackage)
except self.dbapi2.OperationalError:
pass
try:
self.removeProvidedLibraries(idpackage)
except self.dbapi2.OperationalError:
pass
# Remove from dependstable if exists
self._removePackageFromDependsTable(idpackage)
if do_cleanup:
# Cleanups if at least one package has been removed
self.doCleanups()
if do_commit:
self.commitChanges()
def removeMirrorEntries(self, mirrorname):
"""
Remove source packages mirror entries from database for the given
mirror name. This is a representation of Portage's "thirdpartymirrors".
@param mirrorname: mirror name
@type mirrorname: string
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM mirrorlinks WHERE mirrorname = (?)
""",(mirrorname,))
def addMirrors(self, mirrorname, mirrorlist):
"""
Add source package mirror entry to database.
This is a representation of Portage's "thirdpartymirrors".
@param mirrorname: name of the mirror from which "mirrorlist" belongs
@type mirrorname: string
@param mirrorlist: list of URLs belonging to the given mirror name
@type mirrorlist: list
"""
with self.__write_mutex:
self.cursor.executemany("""
INSERT into mirrorlinks VALUES (?,?)
""", [(mirrorname, x,) for x in mirrorlist])
def addCategory(self, category):
"""
Add package category string to repository. Return its identifier
(idcategory).
@param category: name of the category to add
@type category: string
@return: category identifier (idcategory)
@rtype: int
"""
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT into categories VALUES (NULL,?)
""", (category,))
return cur.lastrowid
def addProtect(self, protect):
"""
Add a single, generic CONFIG_PROTECT (not defined as _MASK/whatever
here) path. Return its identifier (idprotect).
@param protect: CONFIG_PROTECT path to add
@type protect: string
@return: protect identifier (idprotect)
@rtype: int
"""
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT into configprotectreference VALUES (NULL,?)
""", (protect,))
return cur.lastrowid
def addSource(self, source):
"""
Add source code package download path to repository. Return its
identifier (idsource).
@param source: source package download path
@type source: string
@return: source identifier (idprotect)
@rtype: int
"""
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT into sourcesreference VALUES (NULL,?)
""", (source,))
return cur.lastrowid
def addDependency(self, dependency):
"""
Add dependency string to repository. Return its identifier
(iddependency).
@param dependency: dependency string
@type dependency: string
@return: dependency identifier (iddependency)
@rtype: int
"""
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT into dependenciesreference VALUES (NULL,?)
""", (dependency,))
return cur.lastrowid
def addKeyword(self, keyword):
"""
Add package SPM keyword string to repository.
Return its identifier (idkeyword).
@param keyword: keyword string
@type keyword: string
@return: keyword identifier (idkeyword)
@rtype: int
"""
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT into keywordsreference VALUES (NULL,?)
""", (keyword,))
return cur.lastrowid
def addUseflag(self, useflag):
"""
Add package USE flag string to repository.
Return its identifier (iduseflag).
@param useflag: useflag string
@type useflag: string
@return: useflag identifier (iduseflag)
@rtype: int
"""
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT into useflagsreference VALUES (NULL,?)
""", (useflag,))
return cur.lastrowid
def addEclass(self, eclass):
"""
Add package SPM Eclass string to repository.
Return its identifier (ideclass).
@param eclass: eclass string
@type eclass: string
@return: eclass identifier (ideclass)
@rtype: int
"""
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT into eclassesreference VALUES (NULL,?)
""", (eclass,))
return cur.lastrowid
def addNeeded(self, needed):
"""
Add package libraries' ELF object NEEDED string to repository.
Return its identifier (idneeded).
@param needed: NEEDED string (as shown in `readelf -d elf.so`)
@type needed: string
@return: needed identifier (idneeded)
@rtype: int
"""
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT into neededreference VALUES (NULL,?)
""", (needed,))
return cur.lastrowid
def addLicense(self, pkglicense):
"""
Add package license name string to repository.
Return its identifier (idlicense).
@param pkglicense: license name string
@type pkglicense: string
@return: license name identifier (idlicense)
@rtype: int
"""
if not self.entropyTools.is_valid_string(pkglicense):
pkglicense = ' ' # workaround for broken license entries
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT into licenses VALUES (NULL,?)
""", (pkglicense,))
return cur.lastrowid
def addCompileFlags(self, chost, cflags, cxxflags):
"""
Add package Compiler flags used to repository.
Return its identifier (idflags).
@param chost: CHOST string
@type chost: string
@param cflags: CFLAGS string
@type cflags: string
@param cxxflags: CXXFLAGS string
@type cxxflags: string
@return: Compiler flags triple identifier (idflags)
@rtype: int
"""
with self.__write_mutex:
cur = self.cursor.execute("""
INSERT into flags VALUES (NULL,?,?,?)
""", (chost,cflags,cxxflags,))
return cur.lastrowid
def setSystemPackage(self, idpackage, do_commit = True):
"""
Mark a package as system package, which means that entropy.client
will deny its removal.
@param idpackage: package identifier
@type idpackage: int
@keyword do_commit: determine whether executing commit or not
@type do_commit: bool
"""
with self.__write_mutex:
self.cursor.execute("""
INSERT into systempackages VALUES (?)
""", (idpackage,))
if do_commit:
self.commitChanges()
def setInjected(self, idpackage, 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 idpackage: package indentifier
@type idpackage: int
@keyword do_commit: determine whether executing commit or not
@type do_commit: bool
"""
with self.__write_mutex:
if not self.isInjected(idpackage):
self.cursor.execute("""
INSERT into injected VALUES (?)
""", (idpackage,))
if do_commit:
self.commitChanges()
def setCreationDate(self, idpackage, date):
"""
Update the creation date for package. Creation date is stored in
string based unix time format.
@param idpackage: package indentifier
@type idpackage: int
@param date: unix time in string form
@type date: string
"""
with self.__write_mutex:
self.cursor.execute("""
UPDATE extrainfo SET datecreation = (?) WHERE idpackage = (?)
""", (str(date), idpackage,))
self.commitChanges()
def setDigest(self, idpackage, digest):
"""
Set package file md5sum for package. This information is used
by entropy.client when downloading packages.
@param idpackage: package indentifier
@type idpackage: int
@param digest: md5 hash for package file
@type digest: string
"""
with self.__write_mutex:
self.cursor.execute("""
UPDATE extrainfo SET digest = (?) WHERE idpackage = (?)
""", (digest, idpackage,))
self.commitChanges()
def setSignatures(self, idpackage, sha1, sha256, sha512):
"""
Set package file extra hashes (sha1, sha256, sha512) for package.
@param idpackage: package indentifier
@type idpackage: 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
"""
with self.__write_mutex:
self.cursor.execute("""
UPDATE packagesignatures SET sha1 = (?), sha256 = (?), sha512 = (?)
WHERE idpackage = (?)
""", (sha1, sha256, sha512, idpackage))
def setDownloadURL(self, idpackage, url):
"""
Set download URL prefix for package.
@param idpackage: package indentifier
@type idpackage: int
@param url: URL prefix to set
@type url: string
"""
with self.__write_mutex:
self.cursor.execute("""
UPDATE extrainfo SET download = (?) WHERE idpackage = (?)
""", (url, idpackage,))
self.commitChanges()
def setCategory(self, idpackage, category):
"""
Set category name for package.
@param idpackage: package indentifier
@type idpackage: int
@param category: category to set
@type category: string
"""
# create new category if it doesn't exist
catid = self.isCategoryAvailable(category)
if catid == -1:
# create category
catid = self.addCategory(category)
with self.__write_mutex:
self.cursor.execute("""
UPDATE baseinfo SET idcategory = (?) WHERE idpackage = (?)
""", (catid, idpackage,))
self.commitChanges()
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
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM categoriesdescription WHERE category = (?)
""", (category,))
for locale in description_data:
mydesc = description_data[locale]
self.cursor.execute("""
INSERT INTO categoriesdescription VALUES (?,?,?)
""", (category, locale, mydesc,))
self.commitChanges()
def setName(self, idpackage, name):
"""
Set name for package.
@param idpackage: package indentifier
@type idpackage: int
@param name: package name
@type name: string
"""
with self.__write_mutex:
self.cursor.execute("""
UPDATE baseinfo SET name = (?) WHERE idpackage = (?)
""", (name, idpackage,))
self.commitChanges()
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
"""
with self.__write_mutex:
self.cursor.execute("""
UPDATE dependenciesreference SET dependency = (?)
WHERE iddependency = (?)
""", (dependency, iddependency,))
self.commitChanges()
def setAtom(self, idpackage, atom):
"""
Set atom string for package. "Atom" is the full, unique name of
a package.
@param idpackage: package indentifier
@type idpackage: int
@param atom: atom string
@type atom: string
"""
with self.__write_mutex:
self.cursor.execute("""
UPDATE baseinfo SET atom = (?) WHERE idpackage = (?)
""", (atom, idpackage,))
self.commitChanges()
def setSlot(self, idpackage, slot):
"""
Set slot string for package. Please refer to Portage SLOT documentation
for more info.
@param idpackage: package indentifier
@type idpackage: int
@param slot: slot string
@type slot: string
"""
with self.__write_mutex:
self.cursor.execute("""
UPDATE baseinfo SET slot = (?) WHERE idpackage = (?)
""", (slot, idpackage,))
self.commitChanges()
def removeLicensedata(self, license_name):
"""
Remove license text for given license name identifier.
@param license_name: available license name identifier
@type license_name: string
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM licensedata WHERE licensename = (?)
""", (license_name,))
def removeDependencies(self, idpackage):
"""
Remove all the dependencies of package.
@param idpackage: package indentifier
@type idpackage: int
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM dependencies WHERE idpackage = (?)
""", (idpackage,))
self.commitChanges()
def insertDependencies(self, idpackage, depdata):
"""
Insert dependencies for package. "depdata" is a dict() with dependency
strings as keys and dependency type as values.
@param idpackage: package indentifier
@type idpackage: int
@param depdata: dependency dictionary
{'app-foo/foo': dep_type_integer, ...}
@type depdata: dict
"""
dcache = set()
add_dep = self.addDependency
is_dep_avail = self.isDependencyAvailable
def mymf(dep):
if dep in dcache:
return 0
iddep = is_dep_avail(dep)
if iddep == -1:
iddep = add_dep(dep)
deptype = 0
if isinstance(depdata, dict):
deptype = depdata[dep]
dcache.add(dep)
return (idpackage, iddep, deptype,)
deps = [x for x in map(mymf, depdata) if type(x) is not int]
with self.__write_mutex:
self.cursor.executemany("""
INSERT into dependencies VALUES (?,?,?)
""", deps)
def insertManualDependencies(self, idpackage, manual_deps):
"""
Insert manually added dependencies to dep. list of package.
@param idpackage: package indentifier
@type idpackage: int
@param manual_deps: list of dependency strings
@type manual_deps: list
"""
mydict = {}
for manual_dep in manual_deps:
mydict[manual_dep] = etpConst['spm']['mdepend_id']
return self.insertDependencies(idpackage, mydict)
def removeContent(self, idpackage):
"""
Remove content metadata for package.
@param idpackage: package indentifier
@type idpackage: int
"""
with self.__write_mutex:
self.cursor.execute("DELETE FROM content WHERE idpackage = (?)", (idpackage,))
self.commitChanges()
def insertContent(self, idpackage, content, already_formatted = False):
"""
Insert content metadata for package. "content" can either be a dict()
or a list of triples (tuples of length 3, (idpackage, path, type,)).
@param idpackage: package indentifier
@type idpackage: int
@param content: content metadata to insert.
{'/path/to/foo': 'obj(content type)',}
or
[(idpackage, 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
"""
with self.__write_mutex:
if already_formatted:
self.cursor.executemany("""
INSERT INTO content VALUES (?,?,?)
""", [(idpackage, x, y,) for a, x, y in content])
else:
self.cursor.executemany("""
INSERT INTO content VALUES (?,?,?)
""", [(idpackage, x, content[x],) for x in content])
def insertProvidedLibraries(self, idpackage, libs_metadata):
"""
Insert library metadata owned by package.
@param idpackage: package indentifier
@type idpackage: int
@param libs_metadata: provided library metadata composed by list of
tuples of length 3 containing library name, path and ELF class.
@type libs_metadata: list
"""
with self.__write_mutex:
self.cursor.executemany("""
INSERT INTO provided_libs VALUES (?,?,?,?)
""", [(idpackage, x, y, z,) for x, y, z in libs_metadata])
def insertAutomergefiles(self, idpackage, 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.
@param idpackage: package indentifier
@type idpackage: int
@param automerge_data: list of tuples of length 2.
[('/path/to/conf/file', 'md5_checksum_string',) ... ]
@type automerge_data: list
"""
with self.__write_mutex:
self.cursor.executemany('INSERT INTO automergefiles VALUES (?,?,?)',
[(idpackage, x, y,) for x, y in automerge_data])
def removeAutomergefiles(self, idpackage):
"""
Remove 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.
@param idpackage: package indentifier
@type idpackage: int
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM automergefiles WHERE idpackage = (?)
""", (idpackage,))
def removeSignatures(self, idpackage):
"""
Remove extra package file hashes (SHA1, SHA256, SHA512) for package.
Entropy package files metadata contains up to 4 hashes:
md5, sha1, sha256, sha512
While md5 is here for historical reasons (being the first supported)
sha1, sha256, sha512 have been added recently and located into a
separate database table called "packagesignatures". Such hashes
can be not available for older packages, so don't be scared, aliens
are not to blame.
@param idpackage: package indentifier
@type idpackage: int
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM packagesignatures WHERE idpackage = (?)
""", (idpackage,))
def removeProvidedLibraries(self, idpackage):
"""
Remove provided libraries metadata from repository for given package
identifier.
@param idpackage: package indentifier
@type idpackage: int
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM provided_libs WHERE idpackage = (?)
""", (idpackage,))
def removeSpmPhases(self, idpackage):
"""
Remove Source Package Manager phases for package.
Entropy can call several Source Package Manager (the PM which Entropy
relies on) package installation/removal phases.
Such phase names are listed here.
@param idpackage: package indentifier
@type idpackage: int
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM packagespmphases WHERE idpackage = (?)
""", (idpackage,))
def insertChangelog(self, category, name, changelog_txt):
"""
Insert package changelog for package (in this case using category +
name as key).
@param category: package category
@type category: string
@param name: package name
@type name: string
@param changelog_txt: changelog text
@type changelog_txt: string
"""
with self.__write_mutex:
mytxt = changelog_txt.encode('raw_unicode_escape')
self.cursor.execute("""
DELETE FROM packagechangelogs WHERE category = (?) AND name = (?)
""", (category, name,))
self.cursor.execute("""
INSERT INTO packagechangelogs VALUES (?,?,?)
""", (category, name, buffer(mytxt),))
def removeChangelog(self, category, name):
"""
Remove ChangeLog for package (in this case using category + name as key)
@param category: package category
@type category: string
@param name: package name
@type name: string
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM packagechangelogs WHERE category = (?) AND name = (?)
""", (category, name,))
def insertLicenses(self, licenses_data):
"""
insert license data (license names and text) into repository.
@param licenses_data: dictionary containing license names as keys and
text as values
@type licenses_data: dict
"""
mylicenses = licenses_data.keys()
def my_mf(mylicense):
return not self.isLicensedataKeyAvailable(mylicense)
def my_mm(mylicense):
lic_data = licenses_data.get(mylicense, '')
# support both utf8 and str input
if isinstance(lic_data, unicode): # encode to str
try:
lic_data = lic_data.encode('raw_unicode_escape')
except (UnicodeDecodeError,):
lic_data = lic_data.encode('utf-8')
return (mylicense, buffer(lic_data), 0,)
with self.__write_mutex:
# set() used after filter to remove duplicates
self.cursor.executemany("""
INSERT into licensedata VALUES (?,?,?)
""", map(my_mm, set(filter(my_mf, mylicenses))))
def insertConfigProtect(self, idpackage, idprotect, mask = False):
"""
Insert CONFIG_PROTECT (configuration files protection) entry identifier
for package. This entry is usually a space separated string of directory
and files which are used to handle user-protected configuration files
or directories, those that are going to be stashed in separate paths
waiting for user merge decisions.
@param idpackage: package indentifier
@type idpackage: int
@param idprotect: configuration files protection identifier
@type idprotect: int
@keyword mask: if True, idproctect will be considered a "mask" entry,
meaning that configuration files starting with paths referenced
by idprotect will be forcefully merged.
@type mask: bool
"""
mytable = 'configprotect'
if mask:
mytable += 'mask'
with self.__write_mutex:
self.cursor.execute("""
INSERT into %s VALUES (?,?)
""" % (mytable,), (idpackage, idprotect,))
def insertMirrors(self, mirrors):
"""
Insert list of "mirror name" and "mirror list" into repository.
The term "mirror" in this case references to Source Package Manager
package download mirrors.
Argument format is like this for historical reasons and may change in
future.
@todo: change argument format
@param mirrors: list of tuples of length 2 containing string as first
item and list as second.
[('openoffice', ['http://openoffice1', 'http://..."],), ...]
@type mirrors: list
"""
for mirrorname, mirrorlist in mirrors:
# remove old
self.removeMirrorEntries(mirrorname)
# add new
self.addMirrors(mirrorname, mirrorlist)
def insertKeywords(self, idpackage, keywords):
"""
Insert keywords for package. Keywords are strings contained in package
metadata stating what architectures or subarchitectures are supported
by package. It is historically used also for masking packages (making
them not available).
@param idpackage: package indentifier
@type idpackage: int
@param keywords: list of keywords
@type keywords: list
"""
def mymf(key):
idkeyword = self.isKeywordAvailable(key)
if idkeyword == -1:
# create category
idkeyword = self.addKeyword(key)
return (idpackage, idkeyword,)
with self.__write_mutex:
self.cursor.executemany("""
INSERT into keywords VALUES (?,?)
""", map(mymf, keywords))
def insertUseflags(self, idpackage, useflags):
"""
Insert Source Package Manager USE (components build) flags for package.
@param idpackage: package indentifier
@type idpackage: int
@param useflags: list of use flags strings
@type useflags: list
"""
def mymf(flag):
iduseflag = self.isUseflagAvailable(flag)
if iduseflag == -1:
# create category
iduseflag = self.addUseflag(flag)
return (idpackage, iduseflag,)
with self.__write_mutex:
self.cursor.executemany("""
INSERT into useflags VALUES (?,?)
""", map(mymf, useflags))
def insertSignatures(self, idpackage, sha1, sha256, sha512):
"""
Insert package file extra hashes (sha1, sha256, sha512) for package.
@param idpackage: package indentifier
@type idpackage: 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
"""
with self.__write_mutex:
self.cursor.execute("""
INSERT INTO packagesignatures VALUES (?,?,?,?)
""", (idpackage, sha1, sha256, sha512))
def insertSpmPhases(self, idpackage, phases):
"""
Insert Source Package Manager phases for package.
Entropy can call several Source Package Manager (the PM which Entropy
relies on) package installation/removal phases.
Such phase names are listed here.
@param idpackage: package indentifier
@type idpackage: int
@param phases: list of available Source Package Manager phases
@type phases: list
"""
with self.__write_mutex:
self.cursor.execute("""
INSERT INTO packagespmphases VALUES (?,?)
""", (idpackage, phases,))
def insertSources(self, idpackage, sources):
"""
Insert source code package download URLs for idpackage.
@param idpackage: package indentifier
@type idpackage: int
@param sources: list of source URLs
@type sources: list
"""
def mymf(source):
if (not source) or (source == "") or \
(not self.entropyTools.is_valid_string(source)):
return 0
idsource = self.isSourceAvailable(source)
if idsource == -1:
idsource = self.addSource(source)
return (idpackage, idsource,)
with self.__write_mutex:
self.cursor.executemany("""
INSERT into sources VALUES (?,?)
""", [x for x in map(mymf, sources) if x != 0])
def insertConflicts(self, idpackage, conflicts):
"""
Insert dependency conflicts for package.
@param idpackage: package indentifier
@type idpackage: int
@param conflicts: list of dep. conflicts
@type conflicts: list
"""
with self.__write_mutex:
self.cursor.executemany("""
INSERT into conflicts VALUES (?,?)
""", [(idpackage, x,) for x in conflicts])
def insertMessages(self, idpackage, messages):
"""
Insert user messages for package.
@param idpackage: package indentifier
@type idpackage: int
@param messages: list of messages
@type messages: list
"""
with self.__write_mutex:
self.cursor.executemany("""
INSERT into messages VALUES (?,?)
""", [(idpackage, x,) for x in messages])
def insertProvide(self, idpackage, provides):
"""
Insert PROVIDE metadata for idpackage.
This has been added for supporting Portage Source Package Manager
old-style meta-packages support.
Packages can provide extra atoms, you can see it like aliases, where
these can be given by multiple packages. This allowed to make available
multiple applications providing the same functionality which depending
packages can reference, without forcefully being bound to a single
package.
@param idpackage: package indentifier
@type idpackage: int
@param provides: list of atom strings
@type provides: list
"""
with self.__write_mutex:
self.cursor.executemany("""
INSERT into provide VALUES (?,?)
""", [(idpackage, x,) for x in provides])
def insertNeeded(self, idpackage, neededs):
"""
Insert package libraries' ELF object NEEDED string for package.
Return its identifier (idneeded).
@param idpackage: package indentifier
@type idpackage: int
@param neededs: list of NEEDED string (as shown in `readelf -d elf.so`)
@type neededs: string
"""
def mymf(needed_data):
needed, elfclass = needed_data
idneeded = self.isNeededAvailable(needed)
if idneeded == -1:
# create eclass
idneeded = self.addNeeded(needed)
return (idpackage, idneeded, elfclass,)
with self.__write_mutex:
self.cursor.executemany("""
INSERT into needed VALUES (?,?,?)
""", map(mymf, neededs))
def insertEclasses(self, idpackage, eclasses):
"""
Insert Source Package Manager used build specification file classes.
The term "eclasses" is derived from Portage.
@param idpackage: package indentifier
@type idpackage: int
@param eclasses: list of classes
@type eclasses: list
"""
def mymf(eclass):
idclass = self.isEclassAvailable(eclass)
if idclass == -1:
idclass = self.addEclass(eclass)
return (idpackage, idclass,)
with self.__write_mutex:
self.cursor.executemany("""
INSERT into eclasses VALUES (?,?)
""", map(mymf, eclasses))
def insertOnDiskSize(self, idpackage, mysize):
"""
Insert on-disk size (bytes) for package.
@param idpackage: package indentifier
@type idpackage: int
@param mysize: package size (bytes)
@type mysize: int
"""
with self.__write_mutex:
self.cursor.execute("""
INSERT into sizes VALUES (?,?)
""", (idpackage, mysize,))
def insertTrigger(self, idpackage, trigger):
"""
Insert built-in trigger script for package, containing
pre-install, post-install, pre-remove, post-remove hooks.
This feature should be considered DEPRECATED, and kept for convenience.
Please use Source Package Manager features if possible.
@param idpackage: package indentifier
@type idpackage: int
@param trigger: trigger file dump
@type trigger: string
"""
with self.__write_mutex:
self.cursor.execute("""
INSERT into triggers VALUES (?,?)
""", (idpackage, buffer(trigger),))
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
"""
with self.__write_mutex:
self.cursor.execute("""
INSERT OR REPLACE INTO entropy_branch_migration VALUES (?,?,?,?,?)
""", (
repository, from_branch,
to_branch, post_migration_md5sum,
post_upgrade_md5sum,
)
)
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
"""
with self.__write_mutex:
self.cursor.execute("""
UPDATE entropy_branch_migration SET post_upgrade_md5sum = (?) WHERE
repository = (?) AND from_branch = (?) AND to_branch = (?)
""", (post_upgrade_md5sum, repository, from_branch, to_branch,))
def bindSpmPackageUid(self, idpackage, spm_package_uid, branch):
"""
Bind Source Package Manager package identifier ("COUNTER" metadata
for Portage) to Entropy package.
If uid <= -2, a new negative UID will be allocated and returned.
Negative UIDs are considered auto-allocated by Entropy.
This is mainly used for binary packages not belonging to any SPM
packages which are just "injected" inside the repository.
@param idpackage: package indentifier
@type idpackage: int
@param spm_package_uid: Source package Manager unique package identifier
@type spm_package_uid: int
@param branch: current running Entropy branch
@type branch: string
@return: uid set
@rtype: int
"""
my_uid = spm_package_uid
if my_uid <= -2:
# special cases
my_uid = self.getNewNegativeSpmUid()
with self.__write_mutex:
try:
self.cursor.execute('INSERT into counters VALUES (?,?,?)',
(my_uid, idpackage, branch,))
except self.dbapi2.IntegrityError:
# we have a PRIMARY KEY we need to remove
self._migrateCountersTable()
self.cursor.execute('INSERT into counters VALUES (?,?,?)',
(my_uid, idpackage, branch,))
except:
if self.dbname == etpConst['clientdbid']:
# force only for client database
if self._doesTableExist("counters"):
raise
self.cursor.execute(
'INSERT into counters VALUES (?,?,?)',
(my_uid, idpackage, branch,))
elif self.dbname.startswith(etpConst['serverdbid']):
raise
return my_uid
def insertSpmUid(self, idpackage, spm_package_uid, branch = None):
"""
Insert Source Package Manager unique package identifier and bind it
to Entropy package identifier given (idpackage). This method is used
by Entropy Client and differs from "bindSpmPackageUid" because
any other colliding idpackage<->uid binding is overwritten by design.
@param idpackage: package indentifier
@type idpackage: int
@param spm_package_uid: Source package Manager unique package identifier
@type spm_package_uid: int
@param branch: current running Entropy branch
@type branch: string
"""
if not branch:
branch = self.db_branch
if not branch:
branch = self.SystemSettings['repositories']['branch']
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM counters WHERE (counter = (?) OR
idpackage = (?)) AND branch = (?);
""", (spm_package_uid, idpackage, branch,))
self.cursor.execute("""
INSERT INTO counters VALUES (?,?,?);
""", (spm_package_uid, idpackage, branch,))
self.commitChanges()
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
"""
with self.__write_mutex:
self.cursor.execute("""
INSERT OR REPLACE INTO trashedcounters VALUES (?)
""", (spm_package_uid,))
def setSpmUid(self, idpackage, spm_package_uid, branch = None):
"""
Update Source Package Manager unique package identifier for given
Entropy package identifier (idpackage).
This method *only* updates a currently available binding setting a new
"spm_package_uid"
@param idpackage: package indentifier
@type idpackage: 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
"""
branchstring = ''
insertdata = [spm_package_uid, idpackage]
if branch:
branchstring = ', branch = (?)'
insertdata.insert(1, branch)
with self.__write_mutex:
try:
self.cursor.execute("""
UPDATE counters SET counter = (?) %s
WHERE idpackage = (?)""" % (branchstring,), insertdata)
except self.dbapi2.Error:
if self.dbname == etpConst['clientdbid']:
raise
self.commitChanges()
def contentDiff(self, idpackage, dbconn, dbconn_idpackage):
"""
Return content metadata difference between two packages.
@param idpackage: package indentifier available in this repository
@type idpackage: int
@param dbconn: other repository class instance
@type dbconn: EntropyRepository
@param dbconn_idpackage: package identifier available in other
repository
@type dbconn_idpackage: int
@return: content difference
@rtype: set
@raise AttributeError: when self instance and dbconn are the same
"""
if self is dbconn:
raise AttributeError("cannot diff inside the same db")
self.connection.text_factory = \
lambda x: unicode(x, "raw_unicode_escape")
# setup random table name
randomtable = "cdiff%s" % (self.entropyTools.get_random_number(),)
while self._doesTableExist(randomtable):
randomtable = "cdiff%s" % (self.entropyTools.get_random_number(),)
# create random table
self.cursor.execute("""
CREATE TEMPORARY TABLE %s ( file VARCHAR )""" % (randomtable,)
)
try:
dbconn.connection.text_factory = \
lambda x: unicode(x, "raw_unicode_escape")
cur = dbconn.cursor.execute("""
SELECT file FROM content WHERE idpackage = (?)
""", (dbconn_idpackage,))
self.cursor.executemany("""
INSERT INTO %s VALUES (?)""" % (randomtable,), cur)
# now compare
cur = self.cursor.execute("""
SELECT file FROM content
WHERE content.idpackage = (?) AND
content.file NOT IN (SELECT file from %s)""" % (randomtable,),
(idpackage,))
# suck back
return self._cur2set(cur)
finally:
self.cursor.execute('DROP TABLE IF EXISTS %s' % (randomtable,))
def doCleanups(self):
"""
Run repository metadata cleanup over unused references.
"""
self.cleanupUseflags()
self.cleanupSources()
self.cleanupEclasses()
self.cleanupNeeded()
self.cleanupDependencies()
self.cleanupChangelogs()
def cleanupUseflags(self):
"""
Cleanup "USE flags" metadata unused references to save space.
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM useflagsreference
WHERE idflag NOT IN (SELECT idflag FROM useflags)""")
def cleanupSources(self):
"""
Cleanup "sources" metadata unused references to save space.
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM sourcesreference
WHERE idsource NOT IN (SELECT idsource FROM sources)""")
def cleanupEclasses(self):
"""
Cleanup "eclass" metadata unused references to save space.
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM eclassesreference
WHERE idclass NOT IN (SELECT idclass FROM eclasses)""")
def cleanupNeeded(self):
"""
Cleanup "needed" metadata unused references to save space.
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM neededreference
WHERE idneeded NOT IN (SELECT idneeded FROM needed)""")
def cleanupDependencies(self):
"""
Cleanup "dependencies" metadata unused references to save space.
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM dependenciesreference
WHERE iddependency NOT IN (SELECT iddependency FROM dependencies)
""")
def cleanupChangelogs(self):
"""
Cleanup "changelog" metadata unused references to save space.
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM packagechangelogs
WHERE category || "/" || name NOT IN
(SELECT categories.category || "/" || baseinfo.name
FROM baseinfo, categories
WHERE baseinfo.idcategory = categories.idcategory)
""")
def getNewNegativeSpmUid(self):
"""
Obtain auto-generated available negative Source Package Manager
package identifier.
@return: new negative spm uid
@rtype: int
"""
try:
cur = self.cursor.execute('SELECT min(counter) FROM counters')
dbcounter = cur.fetchone()
mycounter = 0
if dbcounter:
mycounter = dbcounter[0]
if mycounter >= -1 or mycounter is None:
counter = -2
else:
counter = mycounter-1
except self.dbapi2.Error:
counter = -2 # first available counter
return counter
def getApi(self):
"""
Get Entropy repository API.
@return: Entropy repository API
@rtype: int
"""
cur = self.cursor.execute('SELECT max(etpapi) FROM baseinfo')
api = cur.fetchone()
if api:
return api[0]
return -1
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
"""
cur = self.cursor.execute("""
SELECT dependency FROM dependenciesreference WHERE iddependency = (?)
""", (iddependency,))
dep = cur.fetchone()
if dep:
return dep[0]
def getCategory(self, idcategory):
"""
Get category name from category identifier.
@param idcategory: category identifier
@type idcategory: int
@return: category name
@rtype: string
"""
cur = self.cursor.execute("""
SELECT category from categories WHERE idcategory = (?)
""", (idcategory,))
cat = cur.fetchone()
if cat:
return cat[0]
return cat
def _get_category_description_from_disk(self, category):
"""
Get category name description from Source Package Manager.
@param category: category name
@type category: string
@return: category description
@rtype: string
"""
return get_spm(self).get_package_category_description_metadata(category)
def getIDPackage(self, atom):
"""
Obtain repository package identifier from its atom string.
@param atom: package atom
@type atom: string
@return: idpackage in repository or -1 if not found
@rtype: int
"""
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo WHERE atom = (?)
""", (atom,))
idpackage = cur.fetchone()
if idpackage:
return idpackage[0]
return -1
def getIDPackageFromDownload(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 idpackage which download metadata ends
with the one provided by download_relative_path
@type endswith: bool
@return: idpackage in repository or -1 if not found
@rtype: int
"""
if endswith:
cur = self.cursor.execute("""
SELECT baseinfo.idpackage FROM baseinfo,extrainfo
WHERE extrainfo.download LIKE (?) AND
baseinfo.idpackage = extrainfo.idpackage
""", ("%"+download_relative_path,))
else:
cur = self.cursor.execute("""
SELECT baseinfo.idpackage FROM baseinfo,extrainfo
WHERE extrainfo.download = (?) AND
baseinfo.idpackage = extrainfo.idpackage
""", (download_relative_path,))
idpackage = cur.fetchone()
if idpackage:
return idpackage[0]
return -1
def getIDPackagesFromFile(self, file):
"""
Obtain repository package identifiers for packages owning the provided
path string (file).
@param file: path to file (or directory) to match
@type file: string
@return: list (set) of idpackages found
@rtype: set
"""
cur = self.cursor.execute("""
SELECT idpackage FROM content WHERE file = (?)
""", (file,))
return self._cur2list(cur)
def getIDCategory(self, category):
"""
Obtain category identifier from category name.
@param category: category name
@type category: string
@return: idcategory or -1 if not found
@rtype: int
"""
cur = self.cursor.execute("""
SELECT "idcategory" FROM categories WHERE category = (?)
""", (category,))
idcat = cur.fetchone()
if idcat:
return idcat[0]
return -1
def getVersioningData(self, idpackage):
"""
Get package version information for provided package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: tuple of length 3 composed by (version, tag, revision,)
belonging to idpackage
@rtype: tuple
"""
cur = self.cursor.execute("""
SELECT version, versiontag, revision FROM baseinfo WHERE idpackage = (?)
""", (idpackage,))
return cur.fetchone()
def getStrictData(self, idpackage):
"""
Get a restricted (optimized) set of package metadata for provided
package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: tuple of length 6 composed by
(package key, slot, version, tag, revision, atom)
belonging to idpackage
@rtype: tuple
"""
self.cursor.execute("""
SELECT categories.category || "/" || baseinfo.name,
baseinfo.slot,baseinfo.version,baseinfo.versiontag,
baseinfo.revision,baseinfo.atom FROM baseinfo, categories
WHERE baseinfo.idpackage = (?) AND
baseinfo.idcategory = categories.idcategory""", (idpackage,))
return self.cursor.fetchone()
def getStrictScopeData(self, idpackage):
"""
Get a restricted (optimized) set of package metadata for provided
identifier that can be used to determine the scope of package.
@param idpackage: package indentifier
@type idpackage: int
@return: tuple of length 3 composed by (atom, slot, revision,)
belonging to idpackage
@rtype: tuple
"""
self.cursor.execute("""
SELECT atom, slot, revision FROM baseinfo
WHERE idpackage = (?)""", (idpackage,))
rslt = self.cursor.fetchone()
return rslt
def getScopeData(self, idpackage):
"""
Get a set of package metadata for provided identifier that can be
used to determine the scope of package.
@param idpackage: package indentifier
@type idpackage: int
@return: tuple of length 9 composed by
(atom, category name, name, version,
slot, tag, revision, branch, api,)
belonging to idpackage
@rtype: tuple
"""
self.cursor.execute("""
SELECT
baseinfo.atom,
categories.category,
baseinfo.name,
baseinfo.version,
baseinfo.slot,
baseinfo.versiontag,
baseinfo.revision,
baseinfo.branch,
baseinfo.etpapi
FROM
baseinfo,
categories
WHERE
baseinfo.idpackage = (?)
and baseinfo.idcategory = categories.idcategory
""", (idpackage,))
return self.cursor.fetchone()
def getBaseData(self, idpackage):
"""
Get a set of basic package metadata for provided package identifier.
@param idpackage: package indentifier
@type idpackage: 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 idpackage
@rtype: tuple
"""
sql = """
SELECT
baseinfo.atom,
baseinfo.name,
baseinfo.version,
baseinfo.versiontag,
extrainfo.description,
categories.category,
flags.chost,
flags.cflags,
flags.cxxflags,
extrainfo.homepage,
licenses.license,
baseinfo.branch,
extrainfo.download,
extrainfo.digest,
baseinfo.slot,
baseinfo.etpapi,
extrainfo.datecreation,
extrainfo.size,
baseinfo.revision
FROM
baseinfo,
extrainfo,
categories,
flags,
licenses
WHERE
baseinfo.idpackage = (?)
and baseinfo.idpackage = extrainfo.idpackage
and baseinfo.idcategory = categories.idcategory
and extrainfo.idflags = flags.idflags
and baseinfo.idlicense = licenses.idlicense
"""
self.cursor.execute(sql, (idpackage,))
return self.cursor.fetchone()
def getTriggerInfo(self, idpackage, 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 idpackage: package indentifier
@type idpackage: 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,
'versiontag': versiontag,
'revision': revision,
'branch': branch,
'chost': chost,
'cflags': cflags,
'cxxflags': cxxflags,
'etpapi': etpapi,
'trigger': self.retrieveTrigger(idpackage),
'eclasses': self.retrieveEclasses(idpackage),
'content': pkg_content,
'spm_phases': self.retrieveSpmPhases(idpackage),
}
@rtype: dict
"""
atom, category, name, \
version, slot, versiontag, \
revision, branch, etpapi = self.getScopeData(idpackage)
chost, cflags, cxxflags = self.retrieveCompileFlags(idpackage)
pkg_content = set()
if content:
pkg_content = self.retrieveContent(idpackage)
data = {
'atom': atom,
'category': category,
'name': name,
'version': version,
'versiontag': versiontag,
'revision': revision,
'branch': branch,
'chost': chost,
'cflags': cflags,
'cxxflags': cxxflags,
'etpapi': etpapi,
'trigger': self.retrieveTrigger(idpackage),
'eclasses': self.retrieveEclasses(idpackage),
'content': pkg_content,
'spm_phases': self.retrieveSpmPhases(idpackage),
}
return data
def getPackageData(self, idpackage, get_content = True,
content_insert_formatted = False, trigger_unicode = True):
"""
Reconstruct all the package metadata belonging to provided package
identifier into a dict object.
@param idpackage: package indentifier
@type idpackage: int
@keyword get_content:
@type get_content: bool
@keyword content_insert_formatted:
@type content_insert_formatted: bool
@keyword trigger_unicode:
@type trigger_unicode: 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(idpackage),
'messages': self.retrieveMessages(idpackage),
'trigger': self.retrieveTrigger(idpackage,
get_unicode = trigger_unicode),
'disksize': self.retrieveOnDiskSize(idpackage),
'changelog': self.retrieveChangelog(idpackage),
'injected': self.isInjected(idpackage),
'systempackage': self.isSystemPackage(idpackage),
'config_protect': self.retrieveProtect(idpackage),
'config_protect_mask': self.retrieveProtectMask(idpackage),
'useflags': self.retrieveUseflags(idpackage),
'keywords': self.retrieveKeywords(idpackage),
'sources': sources,
'eclasses': self.retrieveEclasses(idpackage),
'needed': self.retrieveNeeded(idpackage, extended = True),
'provided_libs': self.retrieveProvidedLibraries(idpackage),
'provide': self.retrieveProvide(idpackage),
'conflicts': self.retrieveConflicts(idpackage),
'licensedata': self.retrieveLicensedata(idpackage),
'content': content,
'dependencies': dict((x, y,) for x, y in \
self.retrieveDependencies(idpackage, extended = True)),
'mirrorlinks': [[x,self.retrieveMirrorInfo(x)] for x in mirrornames],
'signatures': signatures,
'spm_phases': self.retrieveSpmPhases(idpackage),
}
@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(idpackage)
except TypeError:
return None
content = {}
if get_content:
content = self.retrieveContent(
idpackage, extended = True,
formatted = True, insert_formatted = content_insert_formatted
)
sources = self.retrieveSources(idpackage)
mirrornames = set()
for x in sources:
if x.startswith("mirror://"):
mirrornames.add(x.split("/")[2])
sha1, sha256, sha512 = self.retrieveSignatures(idpackage)
signatures = {
'sha1': sha1,
'sha256': sha256,
'sha512': sha512,
}
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(idpackage),
'messages': self.retrieveMessages(idpackage),
'trigger': self.retrieveTrigger(idpackage, get_unicode = trigger_unicode),
'disksize': self.retrieveOnDiskSize(idpackage),
'changelog': self.retrieveChangelog(idpackage),
'injected': self.isInjected(idpackage),
'systempackage': self.isSystemPackage(idpackage),
'config_protect': self.retrieveProtect(idpackage),
'config_protect_mask': self.retrieveProtectMask(idpackage),
'useflags': self.retrieveUseflags(idpackage),
'keywords': self.retrieveKeywords(idpackage),
'sources': sources,
'eclasses': self.retrieveEclasses(idpackage),
'needed': self.retrieveNeeded(idpackage, extended = True),
'provided_libs': self.retrieveProvidedLibraries(idpackage),
'provide': self.retrieveProvide(idpackage),
'conflicts': self.retrieveConflicts(idpackage),
'licensedata': self.retrieveLicensedata(idpackage),
'content': content,
'dependencies': dict((x, y,) for x, y in \
self.retrieveDependencies(idpackage, extended = True)),
'mirrorlinks': [[x,self.retrieveMirrorInfo(x)] for x in mirrornames],
'signatures': signatures,
'spm_phases': self.retrieveSpmPhases(idpackage),
}
return data
def _cur2set(self, cur):
mycontent = set()
for x in cur:
mycontent |= set(x)
return mycontent
def _fetchall2set(self, item):
mycontent = set()
for x in item:
mycontent |= set(x)
return mycontent
def _fetchall2list(self, item):
content = []
for x in item:
content += x
return content
def _cur2list(self, cur):
content = []
for x in cur:
content += x
return content
def clearCache(self, depends = False):
"""
Clear on-disk repository cache.
@keyword depends: if True, clear reverse dependencies cache
@type depends: bool
"""
self.live_cache.clear()
def do_clear(name):
"""
docstring_title
@param name:
@type name:
@return:
@rtype:
"""
dump_path = os.path.join(etpConst['dumpstoragedir'], name)
dump_dir = os.path.dirname(dump_path)
if os.path.isdir(dump_dir):
for item in os.listdir(dump_dir):
try: os.remove(os.path.join(dump_dir, item))
except OSError: pass
do_clear("%s/%s/" % (self.dbMatchCacheKey, self.dbname,))
if depends:
do_clear(etpCache['depends_tree'])
do_clear(etpCache['dep_tree'])
do_clear(etpCache['filter_satisfied_deps'])
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
"""
cur = self.cursor.execute("""
SELECT digest FROM treeupdates WHERE repository = (?)
""", (repository,))
mydigest = cur.fetchone()
if mydigest:
return mydigest[0]
return -1
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 3-length tuple
list containing [(command, branch, unix_time,), ...]
@type no_ids_repos: bool
@return: list of tuples
@rtype: list
"""
if no_ids_repos:
self.cursor.execute("""
SELECT command, branch, date FROM treeupdatesactions
""")
else:
self.cursor.execute('SELECT * FROM treeupdatesactions')
return self.cursor.fetchall()
def retrieveTreeUpdatesActions(self, repository, forbranch = None):
"""
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
@keyword forbranch: filter for specific Entropy branch, provide
alternative branch string
@type forbranch: string
@return: list of raw-string commands to run
@rtype: list
"""
if forbranch is None:
forbranch = self.db_branch
if not forbranch:
forbranch = self.SystemSettings['repositories']['branch']
params = (repository,)
branch_string = ''
if forbranch:
branch_string = 'and branch = (?)'
params = (repository, forbranch,)
self.cursor.execute("""
SELECT command FROM treeupdatesactions WHERE
repository = (?) %s order by date""" % (branch_string,), params)
return self._fetchall2list(self.cursor.fetchall())
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
"""
with self.__write_mutex:
self.cursor.execute('DELETE FROM treeupdatesactions')
self.cursor.executemany("""
INSERT INTO treeupdatesactions VALUES (?,?,?,?,?)
""", updates)
self.commitChanges()
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
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM treeupdatesactions WHERE repository = (?)
""", (repository,))
self.commitChanges()
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
"""
with self.__write_mutex:
myupdates = [[repository]+list(x) for x in updates]
self.cursor.executemany("""
INSERT INTO treeupdatesactions VALUES (NULL,?,?,?,?)
""", myupdates)
self.commitChanges()
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
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM treeupdates where repository = (?)
""", (repository,))
self.cursor.execute("""
INSERT INTO treeupdates VALUES (?,?)
""", (repository, digest,))
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
"""
mytime = str(self.entropyTools.get_current_unix_time())
with self.__write_mutex:
myupdates = [
(repository, x, branch, mytime,) for x in actions \
if not self.doesTreeupdatesActionExist(repository, x, branch)
]
self.cursor.executemany("""
INSERT INTO treeupdatesactions VALUES (NULL,?,?,?,?)
""", myupdates)
def doesTreeupdatesActionExist(self, repository, command, branch):
"""
This method should be considered internal and not suited for general
audience.
Return whether provided "treeupdates" action in repository with
provided branch exists.
@param repository: repository identifier
@type repository: string
@param command: treeupdates command
@type command: string
@param branch: branch metadata bound to command argument value given
@type branch: string
@return: if True, provided treeupdates action already exists
@rtype: bool
"""
self.cursor.execute("""
SELECT * FROM treeupdatesactions
WHERE repository = (?) and command = (?)
and branch = (?)""", (repository, command, branch,))
result = self.cursor.fetchone()
if result:
return True
return False
def clearPackageSets(self):
"""
Clear Package sets (group of packages) entries in repository.
"""
self.cursor.execute('DELETE FROM packagesets')
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
"""
mysets = []
for setname in sorted(sets_data):
for dependency in sorted(sets_data[setname]):
try:
mysets.append((unicode(setname), unicode(dependency),))
except (UnicodeDecodeError, UnicodeEncodeError,):
continue
with self.__write_mutex:
self.cursor.executemany('INSERT INTO packagesets VALUES (?,?)',
mysets)
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
"""
# FIXME backward compatibility
if not self._doesTableExist('packagesets'):
return {}
cur = self.cursor.execute("SELECT setname, dependency FROM packagesets")
data = cur.fetchall()
sets = {}
for setname, dependency in data:
obj = sets.setdefault(setname, set())
obj.add(dependency)
return sets
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: set
"""
cur = self.cursor.execute("""
SELECT dependency FROM packagesets WHERE setname = (?)""",
(setname,))
return self._cur2set(cur)
def retrieveSystemPackages(self):
"""
Return a list of package identifiers that are part of the base
system (thus, marked as system packages).
@return: list (set) of system package identifiers
@rtype: set
"""
cur = self.cursor.execute('SELECT idpackage FROM systempackages')
return self._cur2set(cur)
def retrieveAtom(self, idpackage):
"""
Return "atom" metadatum for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: atom string
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT atom FROM baseinfo WHERE idpackage = (?)""", (idpackage,))
atom = cur.fetchone()
if atom:
return atom[0]
def retrieveBranch(self, idpackage):
"""
Return "branch" metadatum for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: branch metadatum
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT branch FROM baseinfo WHERE idpackage = (?)""", (idpackage,))
branch = cur.fetchone()
if branch:
return branch[0]
def retrieveTrigger(self, idpackage, get_unicode = False):
"""
Return "trigger" script content for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@keyword get_unicode: return in unicode format
@type get_unicode: bool
@return: trigger script content
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT data FROM triggers WHERE idpackage = (?)""", (idpackage,))
trigger = cur.fetchone()
if not trigger:
return '' # backward compatibility with <=0.52.x
if not get_unicode:
return trigger[0]
# cross fingers...
return unicode(trigger[0], 'raw_unicode_escape')
def retrieveDownloadURL(self, idpackage):
"""
Return "download URL" metadatum for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: download url metadatum
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT download FROM extrainfo WHERE idpackage = (?)""", (idpackage,))
download = cur.fetchone()
if download:
return download[0]
def retrieveDescription(self, idpackage):
"""
Return "description" metadatum for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: package description
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT description FROM extrainfo WHERE idpackage = (?)
""", (idpackage,))
description = cur.fetchone()
if description:
return description[0]
def retrieveHomepage(self, idpackage):
"""
Return "homepage" metadatum for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: package homepage
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT homepage FROM extrainfo WHERE idpackage = (?)""", (idpackage,))
home = cur.fetchone()
if home:
return home[0]
def retrieveSpmUid(self, idpackage):
"""
Return Source Package Manager unique identifier bound to Entropy
package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: Spm UID or -1 (if not bound, valid for injected packages)
@rtype: int
"""
cur = self.cursor.execute("""
SELECT counters.counter FROM counters,baseinfo
WHERE counters.idpackage = (?) AND
baseinfo.idpackage = counters.idpackage AND
baseinfo.branch = counters.branch""", (idpackage,))
mycounter = cur.fetchone()
if mycounter:
return mycounter[0]
return -1
def retrieveMessages(self, idpackage):
"""
Return "messages" metadatum for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: list of package messages (for making user aware of stuff)
@rtype: list
"""
cur = self.cursor.execute("""
SELECT message FROM messages WHERE idpackage = (?)""", (idpackage,))
return self._cur2list(cur)
def retrieveSize(self, idpackage):
"""
Return "size" metadatum for given package identifier.
"size" refers to Entropy package file size in bytes.
@param idpackage: package indentifier
@type idpackage: int
@return: size of Entropy package for given package identifier
@rtype: int or None
"""
cur = self.cursor.execute("""
SELECT size FROM extrainfo WHERE idpackage = (?)""", (idpackage,))
size = cur.fetchone()
if size:
return size[0]
# in bytes
def retrieveOnDiskSize(self, idpackage):
"""
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 idpackage: package indentifier
@type idpackage: int
@return: on disk size metadatum
@rtype: int
"""
cur = self.cursor.execute("""
SELECT size FROM sizes WHERE idpackage = (?)""", (idpackage,))
size = cur.fetchone()
if size:
return size[0]
return 0
def retrieveDigest(self, idpackage):
"""
Return "digest" metadatum for given package identifier.
"digest" refers to Entropy package file md5 checksum bound to given
package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: md5 checksum for given package identifier
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT digest FROM extrainfo WHERE idpackage = (?)""", (idpackage,))
digest = cur.fetchone()
if digest:
return digest[0]
def retrieveSignatures(self, idpackage):
"""
Return package file extra hashes (sha1, sha256, sha512) for given
package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: tuple of length 3, sha1, sha256, sha512 package extra
hashes if available, otherwise the same but with None as values.
@rtype: tuple
"""
# FIXME backward compatibility
if not self._doesTableExist('packagesignatures'):
return None, None, None
cur = self.cursor.execute("""
SELECT sha1, sha256, sha512 FROM packagesignatures
WHERE idpackage = (?)""", (idpackage,))
data = cur.fetchone()
if data:
return data
return None, None, None
def retrieveName(self, idpackage):
"""
Return "name" metadatum for given package identifier.
Attention: package name != atom, the former is just a subset of the
latter.
@param idpackage: package indentifier
@type idpackage: int
@return: "name" metadatum for given package identifier
@rtype: string or None
"""
self.cursor.execute("""
SELECT name FROM baseinfo WHERE idpackage = (?)
""", (idpackage,))
name = self.cursor.fetchone()
if name:
return name[0]
def retrieveKeySlot(self, idpackage):
"""
Return a tuple composed by package key and slot for given package
identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: tuple of length 2 composed by (package_key, package_slot,)
@rtupe: tuple or None
"""
cur = self.cursor.execute("""
SELECT categories.category || "/" || baseinfo.name,baseinfo.slot
FROM baseinfo,categories
WHERE baseinfo.idpackage = (?) AND
baseinfo.idcategory = categories.idcategory""", (idpackage,))
return cur.fetchone()
def retrieveKeySlotAggregated(self, idpackage):
"""
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 idpackage: package indentifier
@type idpackage: int
@return: package key + ":" + slot string
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT categories.category || "/" || baseinfo.name || ":" ||
baseinfo.slot FROM baseinfo,categories
WHERE baseinfo.idpackage = (?) AND
baseinfo.idcategory = categories.idcategory""", (idpackage,))
data = cur.fetchone()
if data:
return data[0]
def retrieveKeySlotTag(self, idpackage):
"""
Return package key, slot and tag tuple for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: tuple of length 3 providing (package_key, slot, package_tag,)
@rtype: tuple
"""
cur = self.cursor.execute("""
SELECT categories.category || "/" || baseinfo.name, baseinfo.slot,
baseinfo.versiontag FROM baseinfo, categories WHERE
baseinfo.idpackage = (?) AND
baseinfo.idcategory = categories.idcategory""", (idpackage,))
return cur.fetchone()
def retrieveVersion(self, idpackage):
"""
Return package version for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: package version
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT version FROM baseinfo WHERE idpackage = (?)""", (idpackage,))
ver = cur.fetchone()
if ver:
return ver[0]
def retrieveRevision(self, idpackage):
"""
Return package Entropy-revision for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: Entropy-revision for given package indentifier
@rtype: int or None
"""
cur = self.cursor.execute("""
SELECT revision FROM baseinfo WHERE idpackage = (?)
""", (idpackage,))
rev = cur.fetchone()
if rev:
return rev[0]
def retrieveCreationDate(self, idpackage):
"""
Return creation date for given package identifier.
Creation date returned is a string representation of UNIX time format.
@param idpackage: package indentifier
@type idpackage: int
@return: creation date for given package identifier
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT datecreation FROM extrainfo WHERE idpackage = (?)
""", (idpackage,))
date = cur.fetchone()
if date:
return date[0]
def retrieveApi(self, idpackage):
"""
Return Entropy API in use when given package identifier was added.
@param idpackage: package indentifier
@type idpackage: int
@return: Entropy API for given package identifier
@rtype: int or None
"""
cur = self.cursor.execute("""
SELECT etpapi FROM baseinfo WHERE idpackage = (?)
""", (idpackage,))
api = cur.fetchone()
if api:
return api[0]
def retrieveUseflags(self, idpackage):
"""
Return "USE flags" metadatum for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: list (set) of USE flags for given package identifier.
@rtype: set
"""
cur = self.cursor.execute("""
SELECT flagname FROM useflags,useflagsreference
WHERE useflags.idpackage = (?) AND
useflags.idflag = useflagsreference.idflag""", (idpackage,))
return self._cur2set(cur)
def retrieveEclasses(self, idpackage):
"""
Return "eclass" metadatum for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: list (set) of eclasses for given package identifier
@rtype: set
"""
cur = self.cursor.execute("""
SELECT classname FROM eclasses,eclassesreference
WHERE eclasses.idpackage = (?) AND
eclasses.idclass = eclassesreference.idclass""", (idpackage,))
return self._cur2set(cur)
def retrieveSpmPhases(self, idpackage):
"""
Return "Source Package Manager install phases" for given package
identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: "Source Package Manager available install phases" string
@rtype: string or None
"""
# FIXME backward compatibility
if not self._doesTableExist('packagespmphases'):
return None
cur = self.cursor.execute("""
SELECT phases FROM packagespmphases WHERE idpackage = (?)
""", (idpackage,))
spm_phases = cur.fetchone()
if spm_phases:
return spm_phases[0]
def retrieveNeededRaw(self, idpackage):
"""
Return (raw format) "NEEDED" ELF metadata for libraries contained
in given package.
@param idpackage: package indentifier
@type idpackage: int
@return: list (set) of "NEEDED" entries contained in ELF objects
packed into package file
@rtype: set
"""
cur = self.cursor.execute("""
SELECT library FROM needed,neededreference
WHERE needed.idpackage = (?) AND
needed.idneeded = neededreference.idneeded""", (idpackage,))
return self._cur2set(cur)
def retrieveNeeded(self, idpackage, extended = False, format = False):
"""
Return "NEEDED" elf metadata for libraries contained in given package.
@param idpackage: package indentifier
@type idpackage: int
@keyword extended: also return ELF class information for every
library name
@type extended: bool
@keyword format: properly format output, returning a dictionary with
library name as key and ELF class as value
@type format: bool
@return: "NEEDED" metadata for libraries contained in given package.
@rtype: list or set
"""
if extended:
cur = self.cursor.execute("""
SELECT library,elfclass FROM needed,neededreference
WHERE needed.idpackage = (?) AND
needed.idneeded = neededreference.idneeded order by library
""", (idpackage,))
needed = cur.fetchall()
else:
cur = self.cursor.execute("""
SELECT library FROM needed,neededreference
WHERE needed.idpackage = (?) AND
needed.idneeded = neededreference.idneeded ORDER BY library
""", (idpackage,))
needed = self._cur2list(cur)
if extended and format:
return dict((lib, elfclass,) for lib, elfclass in needed)
return needed
def retrieveProvidedLibraries(self, idpackage):
"""
Return list of library names (from NEEDED ELF metadata) provided by
given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: list of tuples of length 2 composed by library name and ELF
class
@rtype: list
"""
# FIXME backward compatibility
if not self._doesTableExist('provided_libs'):
return set()
cur = self.cursor.execute("""
SELECT library, path, elfclass FROM provided_libs
WHERE idpackage = (?)
""", (idpackage,))
return set(cur.fetchall())
def retrieveConflicts(self, idpackage):
"""
Return list of conflicting dependencies for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: list (set) of conflicting package dependencies
@rtype: set
"""
cur = self.cursor.execute("""
SELECT conflict FROM conflicts WHERE idpackage = (?)
""", (idpackage,))
return self._cur2set(cur)
def retrieveProvide(self, idpackage):
"""
Return list of dependencies/atoms are provided by the given package
identifier (see Portage documentation about old-style PROVIDEs).
@param idpackage: package indentifier
@type idpackage: int
@return: list (set) of atoms provided by package
@rtype: set
"""
cur = self.cursor.execute("""
SELECT atom FROM provide WHERE idpackage = (?)
""", (idpackage,))
return self._cur2set(cur)
def retrieveDependenciesList(self, idpackage):
"""
Return list of dependencies, including conflicts for given package
identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: list (set) of dependencies of package
@rtype: set
"""
cur = self.cursor.execute("""
SELECT dependenciesreference.dependency
FROM dependencies, dependenciesreference
WHERE dependencies.idpackage = (?) AND
dependencies.iddependency = dependenciesreference.iddependency
UNION SELECT "!" || conflict FROM conflicts
WHERE idpackage = (?)""", (idpackage, idpackage,))
return self._cur2set(cur)
def retrievePostDependencies(self, idpackage, extended = False):
"""
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 idpackage: package indentifier
@type idpackage: int
@keyword extended: return in extended format
@type extended: bool
"""
return self.retrieveDependencies(idpackage, extended = extended,
deptype = etpConst['spm']['pdepend_id'])
def retrieveManualDependencies(self, idpackage, extended = False):
"""
Return manually added dependencies for given package identifier.
Note: this function is just a wrapper of retrieveDependencies()
providing deptype (dependency type) = manual-dependencies.
@param idpackage: package indentifier
@type idpackage: int
@keyword extended: return in extended format
@type extended: bool
"""
return self.retrieveDependencies(idpackage, extended = extended,
deptype = etpConst['spm']['mdepend_id'])
def retrieveDependencies(self, idpackage, extended = False, deptype = None,
exclude_deptypes = None):
"""
Return dependencies for given package identifier.
@param idpackage: package indentifier
@type idpackage: 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['spm']['*depend_id'] for dependency type
identifiers
@type deptype: bool
@keyword exclude_deptypes: list of dependency types to exclude
@type exclude_deptypes: list
@return: dependencies of given package
@rtype: list or set
"""
searchdata = [idpackage]
depstring = ''
if deptype != None:
depstring = ' and dependencies.type = (?)'
searchdata.append(deptype)
excluded_deptypes_query = ""
if exclude_deptypes != None:
for dep_type in exclude_deptypes:
if not isinstance(dep_type, (int, long,)):
# filter out crap
continue
excluded_deptypes_query += " AND dependencies.type != %s" % (
dep_type,)
if extended:
cur = self.cursor.execute("""
SELECT dependenciesreference.dependency,dependencies.type
FROM dependencies,dependenciesreference
WHERE dependencies.idpackage = (?) AND
dependencies.iddependency =
dependenciesreference.iddependency %s %s""" % (
depstring,excluded_deptypes_query,), searchdata)
return cur.fetchall()
else:
cur = self.cursor.execute("""
SELECT dependenciesreference.dependency
FROM dependencies,dependenciesreference
WHERE dependencies.idpackage = (?) AND
dependencies.iddependency =
dependenciesreference.iddependency %s %s""" % (
depstring,excluded_deptypes_query,), searchdata)
return self._cur2set(cur)
def retrieveIdDependencies(self, idpackage):
"""
Return list of dependency identifiers for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: list (set) of dependency identifiers
@rtype: set
"""
cur = self.cursor.execute("""
SELECT iddependency FROM dependencies WHERE idpackage = (?)
""", (idpackage,))
return self._cur2set(cur)
def retrieveKeywords(self, idpackage):
"""
Return package SPM keyword list for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: list (set) of keywords for given package identifier
@rtype: set
"""
cur = self.cursor.execute("""
SELECT keywordname FROM keywords,keywordsreference
WHERE keywords.idpackage = (?) AND
keywords.idkeyword = keywordsreference.idkeyword""", (idpackage,))
return self._cur2set(cur)
def retrieveProtect(self, idpackage):
"""
Return CONFIG_PROTECT (configuration file protection) string
(containing a list of space reparated paths) metadata for given
package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: CONFIG_PROTECT string
@rtype: string
"""
cur = self.cursor.execute("""
SELECT protect FROM configprotect,configprotectreference
WHERE configprotect.idpackage = (?) AND
configprotect.idprotect = configprotectreference.idprotect
""", (idpackage,))
protect = cur.fetchone()
if protect:
return protect[0]
return ''
def retrieveProtectMask(self, idpackage):
"""
Return CONFIG_PROTECT_MASK (mask for configuration file protection)
string (containing a list of space reparated paths) metadata for given
package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: CONFIG_PROTECT_MASK string
@rtype: string
"""
self.cursor.execute("""
SELECT protect FROM configprotectmask,configprotectreference
WHERE idpackage = (?) AND
configprotectmask.idprotect = configprotectreference.idprotect
""", (idpackage,))
protect = self.cursor.fetchone()
if protect:
return protect[0]
return ''
def retrieveSources(self, idpackage, extended = False):
"""
Return source package URLs for given package identifier.
"source" as in source code.
@param idpackage: package indentifier
@type idpackage: 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 (set) of
source package URLs.
@rtype: dict or set
"""
cur = self.cursor.execute("""
SELECT sourcesreference.source FROM sources, sourcesreference
WHERE idpackage = (?) AND
sources.idsource = sourcesreference.idsource
""", (idpackage,))
sources = self._cur2set(cur)
if not extended:
return sources
source_data = {}
mirror_str = "mirror://"
for source in sources:
source_data[source] = set()
if source.startswith(mirror_str):
mirrorname = source.split("/")[2]
mirror_url = source.split("/", 3)[3:][0]
source_data[source] |= set(
[os.path.join(url, mirror_url) for url in \
self.retrieveMirrorInfo(mirrorname)])
else:
source_data[source].add(source)
return source_data
def retrieveAutomergefiles(self, idpackage, 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 idpackage: package indentifier
@type idpackage: 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: list or set
"""
# FIXME backward compatibility
if not self._doesTableExist('automergefiles'):
self._createAutomergefilesTable()
# like portage does
self.connection.text_factory = lambda x: \
unicode(x, "raw_unicode_escape")
cur = self.cursor.execute("""
SELECT configfile, md5 FROM automergefiles WHERE idpackage = (?)
""", (idpackage,))
data = cur.fetchall()
if get_dict:
data = dict(((x, y,) for x, y in data))
return data
def retrieveContent(self, idpackage, extended = False, contentType = None,
formatted = False, insert_formatted = False, order_by = ''):
"""
Return files contained in given package.
@param idpackage: package indentifier
@type idpackage: int
@keyword extended: return in extended format
@type extended: bool
@keyword contentType: only return given entry type, which can be:
"obj", "sym" or "dir"
@type contentType: int
@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 "idpackage"
@type order_by: string
@return: content metadata
@rtype: dict or list or set
"""
extstring = ''
if extended:
extstring = ",type"
extstring_idpackage = ''
if insert_formatted:
extstring_idpackage = 'idpackage,'
searchkeywords = [idpackage]
contentstring = ''
if contentType:
searchkeywords.append(contentType)
contentstring = ' and type = (?)'
order_by_string = ''
if order_by:
order_by_string = ' order by %s' % (order_by,)
did_try = False
while 1:
try:
cur = self.cursor.execute("""
SELECT %s file%s FROM content WHERE idpackage = (?) %s%s""" % (
extstring_idpackage, extstring,
contentstring, order_by_string,),
searchkeywords)
if extended and insert_formatted:
fl = cur.fetchall()
elif extended and formatted:
fl = {}
items = cur.fetchone()
while items:
fl[items[0]] = items[1]
items = cur.fetchone()
elif extended:
fl = cur.fetchall()
else:
if order_by:
fl = self._cur2list(cur)
else:
fl = self._cur2set(cur)
break
except self.dbapi2.OperationalError:
if did_try:
raise
did_try = True
# FIXME support for old entropy db entries, which were
# not inserted in utf-8
self.connection.text_factory = lambda x: \
unicode(x, "raw_unicode_escape")
continue
return fl
def retrieveChangelog(self, idpackage):
"""
Return Source Package Manager ChangeLog for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: ChangeLog content
@rtype: string or None
"""
# FIXME backward compatibility
if not self._doesTableExist('packagechangelogs'):
return None
cur = self.cursor.execute("""
SELECT packagechangelogs.changelog
FROM packagechangelogs, baseinfo, categories
WHERE baseinfo.idpackage = (?) AND
baseinfo.idcategory = categories.idcategory AND
packagechangelogs.name = baseinfo.name AND
packagechangelogs.category = categories.category""", (idpackage,))
changelog = cur.fetchone()
if changelog:
changelog = changelog[0]
try:
return unicode(changelog, 'raw_unicode_escape')
except UnicodeDecodeError:
return unicode(changelog, 'utf-8')
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
"""
# FIXME backward compatibility
if not self._doesTableExist('packagechangelogs'):
return None
self.connection.text_factory = lambda x: \
unicode(x, "raw_unicode_escape")
cur = self.cursor.execute("""
SELECT changelog FROM packagechangelogs WHERE category = (?)AND
name = (?)""", (category, name,))
changelog = cur.fetchone()
if changelog:
return unicode(changelog[0], 'raw_unicode_escape')
def retrieveSlot(self, idpackage):
"""
Return "slot" metadatum for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: package slot
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT slot FROM baseinfo WHERE idpackage = (?)""", (idpackage,))
slot = cur.fetchone()
if slot:
return slot[0]
def retrieveVersionTag(self, idpackage):
"""
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 idpackage: package indentifier
@type idpackage: int
@return: tag string
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT versiontag FROM baseinfo WHERE idpackage = (?)
""", (idpackage,))
vtag = cur.fetchone()
if vtag:
return vtag[0]
def retrieveMirrorInfo(self, mirrorname):
"""
Return available mirror URls for given mirror name.
@param mirrorname: mirror name (for eg. "openoffice")
@type mirrorname: string
@return: list (set) of URLs providing the "openoffice" mirroring service
@rtype: set
"""
cur = self.cursor.execute("""
SELECT mirrorlink FROM mirrorlinks WHERE mirrorname = (?)
""", (mirrorname,))
return self._cur2set(cur)
def retrieveCategory(self, idpackage):
"""
Return category name for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: category where package is in
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT category FROM baseinfo,categories
WHERE baseinfo.idpackage = (?) AND
baseinfo.idcategory = categories.idcategory""", (idpackage,))
cat = cur.fetchone()
if cat:
return cat[0]
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
"""
cur = self.cursor.execute("""
SELECT description, locale FROM categoriesdescription
WHERE category = (?)
""", (category,))
return dict((locale, desc,) for desc, locale in cur.fetchall())
def retrieveLicensedata(self, idpackage):
"""
Return license metadata for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: dictionary composed by license name as key and license text
as value
@rtype: dict
"""
licenses = self.retrieveLicense(idpackage)
if licenses is None:
return {}
licdata = {}
for licname in licenses.split():
if not licname.strip():
continue
if not self.entropyTools.is_valid_string(licname):
continue
cur = self.cursor.execute("""
SELECT text FROM licensedata WHERE licensename = (?)
""", (licname,))
lictext = cur.fetchone()
if lictext is not None:
lictext = lictext[0]
try:
licdata[licname] = unicode(lictext, 'raw_unicode_escape')
except UnicodeDecodeError:
licdata[licname] = unicode(lictext, 'utf-8')
return licdata
def retrieveLicensedataKeys(self, idpackage):
"""
Return license names available for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: list (set) of license names which text is available in
repository
@rtype: set
"""
licenses = self.retrieveLicense(idpackage)
if licenses is None:
return set()
licdata = set()
for licname in licenses.split():
if not licname.strip():
continue
if not self.entropyTools.is_valid_string(licname):
continue
cur = self.cursor.execute("""
SELECT licensename FROM licensedata WHERE licensename = (?)
""", (licname,))
lic_id = cur.fetchone()
if lic_id:
licdata.add(lic_id[0])
return licdata
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
"""
self.connection.text_factory = lambda x: \
unicode(x, "raw_unicode_escape")
cur = self.cursor.execute("""
SELECT text FROM licensedata WHERE licensename = (?)
""", (license_name,))
text = cur.fetchone()
if text:
return str(text[0])
def retrieveLicense(self, idpackage):
"""
Return "license" metadatum for given package identifier.
@param idpackage: package indentifier
@type idpackage: int
@return: license string
@rtype: string or None
"""
cur = self.cursor.execute("""
SELECT license FROM baseinfo,licenses
WHERE baseinfo.idpackage = (?) AND
baseinfo.idlicense = licenses.idlicense""", (idpackage,))
licname = cur.fetchone()
if licname:
return licname[0]
def retrieveCompileFlags(self, idpackage):
"""
Return Compiler flags during building of package.
(CHOST, CXXFLAGS, LDFLAGS)
@param idpackage: package indentifier
@type idpackage: int
@return: tuple of length 3 composed by (CHOST, CFLAGS, CXXFLAGS)
@rtype: tuple
"""
self.cursor.execute("""
SELECT chost,cflags,cxxflags FROM flags,extrainfo
WHERE extrainfo.idpackage = (?) AND
extrainfo.idflags = flags.idflags""", (idpackage,))
flags = self.cursor.fetchone()
if not flags:
flags = ("N/A", "N/A", "N/A",)
return flags
def retrieveReverseDependencies(self, idpackage, atoms = False,
key_slot = False, exclude_deptypes = None):
"""
Return reverse (or inverse) dependencies for given package.
@param idpackage: package indentifier
@type idpackage: 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
@type exclude_deptypes: iterable
@return: reverse dependency list
@rtype: list or set
"""
# WARNING: never remove this, otherwise equo.db
# (client database) dependstable will be always broken (trust me)
# sanity check on the table
if not self._isDependsTableSane(): # is empty, need generation
self.regenerateReverseDependenciesMetadata(verbose = False)
excluded_deptypes_query = ""
if exclude_deptypes != None:
for dep_type in exclude_deptypes:
excluded_deptypes_query += " AND dependencies.type != %s" % (
dep_type,)
if atoms:
cur = self.cursor.execute("""
SELECT baseinfo.atom FROM dependstable,dependencies,baseinfo
WHERE dependstable.idpackage = (?) AND
dependstable.iddependency = dependencies.iddependency AND
baseinfo.idpackage = dependencies.idpackage %s""" % (
excluded_deptypes_query,), (idpackage,))
result = self._cur2set(cur)
elif key_slot:
cur = self.cursor.execute("""
SELECT categories.category || "/" || baseinfo.name,baseinfo.slot
FROM baseinfo,categories,dependstable,dependencies
WHERE dependstable.idpackage = (?) AND
dependstable.iddependency = dependencies.iddependency AND
baseinfo.idpackage = dependencies.idpackage AND
categories.idcategory = baseinfo.idcategory %s""" % (
excluded_deptypes_query,), (idpackage,))
result = cur.fetchall()
else:
cur = self.cursor.execute("""
SELECT dependencies.idpackage FROM dependstable,dependencies
WHERE dependstable.idpackage = (?) AND
dependstable.iddependency = dependencies.iddependency %s""" % (
excluded_deptypes_query,), (idpackage,))
result = self._cur2set(cur)
return result
def retrieveUnusedIdpackages(self):
"""
Return packages (through their identifiers) not referenced by any
other as dependency (unused packages).
@return: unused idpackages ordered by atom
@rtype: list
"""
# WARNING: never remove this, otherwise equo.db (client database)
# dependstable will be always broken (trust me)
# sanity check on the table
if not self._isDependsTableSane(): # is empty, need generation
self.regenerateReverseDependenciesMetadata(verbose = False)
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo
WHERE idpackage NOT IN (SELECT idpackage FROM dependstable)
ORDER BY atom
""")
return self._cur2list(cur)
def isAtomAvailable(self, atom):
"""
Return whether given atom is available in repository.
@param atom: package atom
@type atom: string
@return: idpackage or -1 if not found
@rtype: int
"""
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo WHERE atom = (?)""", (atom,))
result = cur.fetchone()
if result:
return result[0]
return -1
def areIdpackagesAvailable(self, idpackages):
"""
Return whether list of package identifiers are available.
They must be all available to return True
@param idpackages: list of package indentifiers
@type idpackages: iterable
@return: availability (True if all are available)
@rtype: bool
"""
sql = """SELECT count(idpackage) FROM baseinfo
WHERE idpackage IN (%s)""" % (','.join(
[str(x) for x in set(idpackages)]),
)
cur = self.cursor.execute(sql)
count = cur.fetchone()[0]
if count != len(idpackages):
return False
return True
def isIdpackageAvailable(self, idpackage):
"""
Return whether given package identifier is available in repository.
@param idpackage: package indentifier
@type idpackage: int
@return: availability (True if available)
@rtype: bool
"""
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo WHERE idpackage = (?)""", (idpackage,))
result = cur.fetchone()
if not result:
return False
return True
def isCategoryAvailable(self, category):
"""
Return whether given category is available in repository.
@param category: category name
@type category: string
@return: availability (True if available)
@rtype: bool
"""
cur = self.cursor.execute("""
SELECT idcategory FROM categories WHERE category = (?)""", (category,))
result = cur.fetchone()
if result:
return result[0]
return -1
def isProtectAvailable(self, protect):
"""
Return whether given CONFIG_PROTECT* entry is available in repository.
@param protect: CONFIG_PROTECT* entry (path to a protected directory
or file that won't be overwritten by Entropy Client during
package merge)
@type protect: string
@return: availability (True if available)
@rtype: bool
"""
cur = self.cursor.execute("""
SELECT idprotect FROM configprotectreference WHERE protect = (?)
""", (protect,))
result = cur.fetchone()
if result:
return result[0]
return -1
def isFileAvailable(self, myfile, get_id = False):
"""
Return whether given file path is available in repository (owned by
one or more packages).
@param myfile: path to file or directory
@type myfile: string
@keyword get_id: return list (set) of idpackages owning myfile
@type get_id: bool
@return: availability (True if available), when get_id is True,
it returns a list (set) of idpackages owning myfile
@rtype: bool or set
"""
cur = self.cursor.execute("""
SELECT idpackage FROM content WHERE file = (?)""", (myfile,))
result = cur.fetchall()
if get_id:
return self._fetchall2set(result)
elif result:
return True
return False
def resolveNeeded(self, needed, elfclass = -1, extended = False):
"""
Resolve NEEDED ELF entry (a library name) to idpackages 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 list of tuple of length 2, first element
is idpackage, second is actual library path
@type extended: bool
@return: list of packages owning given library
@rtype: list or set
"""
args = (needed,)
elfclass_txt = ''
if elfclass != -1:
elfclass_txt = ' AND provided_libs.elfclass = (?)'
args = (needed, elfclass,)
if extended:
cur = self.cursor.execute("""
SELECT idpackage, path FROM provided_libs
WHERE library = (?)""" + elfclass_txt, args)
return cur.fetchall()
cur = self.cursor.execute("""
SELECT idpackage FROM provided_libs
WHERE library = (?)""" + elfclass_txt, args)
return self._cur2set(cur)
def isSourceAvailable(self, source):
"""
Return whether given source package URL is available in repository.
Returns source package URL identifier (idsource).
@param source: source package URL
@type source: string
@return: source package URL identifier (idsource) or -1 if not found
@rtype: int
"""
cur = self.cursor.execute("""
SELECT idsource FROM sourcesreference WHERE source = (?)""", (source,))
result = cur.fetchone()
if result:
return result[0]
return -1
def isDependencyAvailable(self, dependency):
"""
Return whether given dependency string is available in repository.
Returns dependency identifier (iddependency).
@param dependency: dependency string
@type dependency: string
@return: dependency identifier (iddependency) or -1 if not found
@rtype: int
"""
cur = self.cursor.execute("""
SELECT iddependency FROM dependenciesreference WHERE dependency = (?)
""", (dependency,))
result = cur.fetchone()
if result:
return result[0]
return -1
def isKeywordAvailable(self, keyword):
"""
Return whether keyword string is available in repository.
Returns keyword identifier (idkeyword)
@param keyword: keyword string
@type keyword: string
@return: keyword identifier (idkeyword) or -1 if not found
@rtype: int
"""
cur = self.cursor.execute("""
SELECT idkeyword FROM keywordsreference WHERE keywordname = (?)
""", (keyword,))
result = cur.fetchone()
if result:
return result[0]
return -1
def isUseflagAvailable(self, useflag):
"""
Return whether USE flag name is available in repository.
Returns USE flag identifier (idflag).
@param useflag: USE flag name
@type useflag: string
@return: USE flag identifier or -1 if not found
@rtype: int
"""
cur = self.cursor.execute("""
SELECT idflag FROM useflagsreference WHERE flagname = (?)
""", (useflag,))
result = cur.fetchone()
if result:
return result[0]
return -1
def isEclassAvailable(self, eclass):
"""
Return whether eclass name is available in repository.
Returns Eclass identifier (idclass)
@param eclass: eclass name
@type eclass: string
@return: Eclass identifier or -1 if not found
@rtype: int
"""
cur = self.cursor.execute("""
SELECT idclass FROM eclassesreference WHERE classname = (?)
""", (eclass,))
result = cur.fetchone()
if result:
return result[0]
return -1
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
"""
cur = self.cursor.execute("""
SELECT idneeded FROM neededreference WHERE library = (?)
""", (needed,))
result = cur.fetchone()
if result:
return result[0]
return -1
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
"""
cur = self.cursor.execute("""
SELECT counter FROM counters WHERE counter = (?)
""", (spm_uid,))
result = cur.fetchone()
if result:
return True
return False
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
"""
cur = self.cursor.execute("""
SELECT counter FROM trashedcounters WHERE counter = (?)""", (spm_uid,))
result = cur.fetchone()
if result:
return True
return False
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
"""
cur = self.cursor.execute("""
SELECT licensename FROM licensedata WHERE licensename = (?)
""", (license_name,))
result = cur.fetchone()
if not result:
return False
return True
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
"""
cur = self.cursor.execute("""
SELECT licensename FROM licenses_accepted WHERE licensename = (?)
""", (license_name,))
result = cur.fetchone()
if not result:
return False
return True
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
@param license_name: license name
@type license_name: string
@todo: check if readOnly is really required
@todo: check if is_user_in_entropy_group is really required
"""
if self.readOnly:
return
if not self.entropyTools.is_user_in_entropy_group():
return
with self.__write_mutex:
self.cursor.execute("""
INSERT OR IGNORE INTO licenses_accepted VALUES (?)
""", (license_name,))
self.commitChanges()
def isLicenseAvailable(self, pkglicense):
"""
Return whether license metdatatum (NOT license name) is available
in repository.
@param pkglicense: "license" package metadatum (returned by
retrieveLicense)
@type pkglicense: string
@return: "license" metadatum identifier (idlicense)
@rtype: int
"""
if not self.entropyTools.is_valid_string(pkglicense):
pkglicense = ' '
cur = self.cursor.execute("""
SELECT idlicense FROM licenses WHERE license = (?)
""", (pkglicense,))
result = cur.fetchone()
if result:
return result[0]
return -1
def isSystemPackage(self, idpackage):
"""
Return whether package is part of core system (though, a system
package).
@param idpackage: package indentifier
@type idpackage: int
@return: if True, package is part of core system
@rtype: bool
"""
cur = self.cursor.execute("""
SELECT idpackage FROM systempackages WHERE idpackage = (?)
""", (idpackage,))
result = cur.fetchone()
if result:
return True
return False
def isInjected(self, idpackage):
"""
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 idpackage: package indentifier
@type idpackage: int
@return: injection status (True if injected)
@rtype: bool
"""
cur = self.cursor.execute("""
SELECT idpackage FROM injected WHERE idpackage = (?)
""", (idpackage,))
result = cur.fetchone()
if result:
return True
return False
def areCompileFlagsAvailable(self, chost, cflags, cxxflags):
"""
Return whether given Compiler FLAGS are available in repository.
@param chost: CHOST flag
@type chost: string
@param cflags: CFLAGS flag
@type cflags: string
@param cxxflags: CXXFLAGS flag
@type cxxflags: string
@return: availability (True if available)
@rtype: bool
"""
cur = self.cursor.execute("""
SELECT idflags FROM flags WHERE chost = (?)
AND cflags = (?) AND cxxflags = (?)""",
(chost, cflags, cxxflags,)
)
result = cur.fetchone()
if result:
return result[0]
return -1
def searchBelongs(self, file, like = False):
"""
Search packages which given file path belongs to.
@param file: file path to search
@type file: string
@keyword like: do not match exact case
@type like: bool
@return: list (set) of package identifiers owning given file
@rtype: set
"""
if like:
cur = self.cursor.execute("""
SELECT content.idpackage FROM content,baseinfo
WHERE file LIKE (?) AND
content.idpackage = baseinfo.idpackage""", (file,))
else:
cur = self.cursor.execute("""SELECT content.idpackage
FROM content, baseinfo WHERE file = (?)
AND content.idpackage = baseinfo.idpackage""", (file,))
return self._cur2set(cur)
def searchEclassedPackages(self, eclass, atoms = False): # atoms = return atoms directly
"""
Search packages which their Source Package Manager counterpar are using
given eclass.
@param eclass: eclass name to search
@type eclass: string
@keyword atoms: return list of atoms instead of package identifiers
@type atoms: bool
@return: list of packages using given eclass
@rtype: set or list
"""
if atoms:
cur = self.cursor.execute("""
SELECT baseinfo.atom,eclasses.idpackage
FROM baseinfo, eclasses, eclassesreference
WHERE eclassesreference.classname = (?) AND
eclassesreference.idclass = eclasses.idclass AND
eclasses.idpackage = baseinfo.idpackage""", (eclass,))
return cur.fetchall()
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo WHERE versiontag = (?)""", (eclass,))
return self._cur2set(cur)
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: set or list
"""
if atoms:
cur = self.cursor.execute("""
SELECT atom, idpackage FROM baseinfo WHERE versiontag = (?)
""", (tag,))
return cur.fetchall()
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo WHERE versiontag = (?)
""", (tag,))
return self._cur2set(cur)
def searchLicenses(self, mylicense, caseSensitive = False, atoms = False):
"""
Search packages using given license (mylicense).
@param mylicense: license name to search
@type mylicense: string
@keyword caseSensitive: search in case sensitive mode (default off)
@type caseSensitive: bool
@keyword atoms: return list of atoms instead of package identifiers
@type atoms: bool
@return: list of packages using given license
@rtype: set or list
@todo: check if is_valid_string is really required
"""
if not self.entropyTools.is_valid_string(mylicense):
return []
request = "baseinfo.idpackage"
if atoms:
request = "baseinfo.atom,baseinfo.idpackage"
if caseSensitive:
cur = self.cursor.execute("""
SELECT %s FROM baseinfo,licenses
WHERE licenses.license LIKE (?) AND
licenses.idlicense = baseinfo.idlicense
""" % (request,), ("%"+mylicense+"%",))
else:
cur = self.cursor.execute("""
SELECT %s FROM baseinfo,licenses
WHERE LOWER(licenses.license) LIKE (?) AND
licenses.idlicense = baseinfo.idlicense
""" % (request,), ("%"+mylicense+"%".lower(),))
if atoms:
return cur.fetchall()
return self._cur2set(cur)
def searchSlottedPackages(self, slot, atoms = False):
"""
Search packages with given slot string.
@param slot: slot to search
@type slot: string
@keyword atoms: return list of atoms instead of package identifiers
@type atoms: bool
@return: list of packages using given slot
@rtype: set or list
"""
if atoms:
cur = self.cursor.execute("""
SELECT atom,idpackage FROM baseinfo WHERE slot = (?)
""", (slot,))
return cur.fetchall()
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo WHERE slot = (?)""", (slot,))
return self._cur2set(cur)
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 (set) of package identifiers
@rtype: set
"""
cat, name = key.split("/")
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo, categories
WHERE baseinfo.idcategory = categories.idcategory AND
categories.category = (?) AND
baseinfo.name = (?) AND
baseinfo.slot = (?)""", (cat, name, slot,))
return cur.fetchall()
def searchNeeded(self, needed, elfclass = -1, like = False):
"""
Search packages that need given NEEDED ELF entry (library name).
@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 (set) of package identifiers
@rtype: set
"""
elfsearch = ''
search_args = (needed,)
if elfclass != -1:
elfsearch = ' AND needed.elfclass = (?)'
search_args = (needed, elfclass,)
if like:
cur = self.cursor.execute("""
SELECT needed.idpackage FROM needed,neededreference
WHERE library LIKE (?) %s AND
needed.idneeded = neededreference.idneeded
""" % (elfsearch,), search_args)
else:
cur = self.cursor.execute("""
SELECT needed.idpackage FROM needed,neededreference
WHERE library = (?) %s AND
needed.idneeded = neededreference.idneeded
""" % (elfsearch,), search_args)
return self._cur2set(cur)
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 of dependency identifiers (if multi is True) or
strings (if strings is True) or dependency identifier
@rtype: int or set
"""
sign = "="
if like:
sign = "LIKE"
dep = "%"+dep+"%"
item = 'iddependency'
if strings:
item = 'dependency'
cur = self.cursor.execute("""
SELECT %s FROM dependenciesreference WHERE dependency %s (?)
""" % (item, sign,), (dep,))
if multi:
return self._cur2set(cur)
iddep = cur.fetchone()
if iddep:
return iddep[0]
return -1
def searchIdpackageFromIddependency(self, iddep):
"""
Search package identifiers owning dependency given (in form of
dependency identifier).
@param iddep: dependency identifier
@type iddep: int
@return: list (set) of package identifiers owning given dependency
identifier
@rtype: set
"""
cur = self.cursor.execute("""
SELECT idpackage FROM dependencies WHERE iddependency = (?)
""", (iddep,))
return self._cur2set(cur)
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 (set) of package sets available matching given keyword
@rtype: set
"""
cur = self.cursor.execute("""
SELECT DISTINCT(setname) FROM packagesets WHERE setname LIKE (?)
""", ("%"+keyword+"%",))
return self._cur2set(cur)
def searchSimilarPackages(self, mystring, atom = False):
"""
Search similar packages (basing on package string given by mystring
argument) using SOUNDEX algorithm (ahhh Google...).
@param mystring: package string to search
@type mystring: string
@keyword atom: return full atoms instead of package names
@type atom: bool
@return: list of similar package names
@rtype: set
"""
s_item = 'name'
if atom:
s_item = 'atom'
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo
WHERE soundex(%s) = soundex((?)) ORDER BY %s
""" % (s_item, s_item,), (mystring,))
return self._cur2list(cur)
def searchPackages(self, keyword, sensitive = False, slot = None,
tag = None, order_by = 'atom', 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", "name" or "version"
@type order_by: string
@keyword just_id: just return package identifiers (returning set())
@type just_id: bool
@return: packages found matching given search criterias
@rtype: set or list
"""
searchkeywords = ["%"+keyword+"%"]
slotstring = ''
if slot:
searchkeywords.append(slot)
slotstring = ' and slot = (?)'
tagstring = ''
if tag:
searchkeywords.append(tag)
tagstring = ' and versiontag = (?)'
order_by_string = ''
if order_by in ("atom", "idpackage", "branch",):
order_by_string = ' order by %s' % (order_by,)
search_elements = 'atom,idpackage,branch'
if just_id:
search_elements = 'idpackage'
if sensitive:
cur = self.cursor.execute("""
SELECT %s FROM baseinfo WHERE atom LIKE (?) %s %s %s""" % (
search_elements, slotstring, tagstring, order_by_string,),
searchkeywords
)
else:
cur = self.cursor.execute("""
SELECT %s FROM baseinfo WHERE
LOWER(atom) LIKE (?) %s %s %s""" % (
search_elements, slotstring, tagstring, order_by_string,),
searchkeywords
)
if just_id:
return self._cur2list(cur)
return cur.fetchall()
def searchProvide(self, keyword, slot = None, tag = None, justid = False):
"""
Search in old-style Portage PROVIDE metadata.
WARNING: this method is deprecated and will be removed someday.
@param keyword: search term
@type keyword: string
@keyword slot: match given package slot
@type slot: string
@keyword tag: match given package tag
@type tag: string
@keyword justid: return list of package identifiers (set())
@type justid: bool
@return: found PROVIDE metadata
@rtype: list or set
"""
searchkeywords = [keyword]
slotstring = ''
if slot:
searchkeywords.append(slot)
slotstring = ' and baseinfo.slot = (?)'
tagstring = ''
if tag:
searchkeywords.append(tag)
tagstring = ' and baseinfo.versiontag = (?)'
atomstring = ''
if not justid:
atomstring = 'baseinfo.atom,'
cur = self.cursor.execute("""
SELECT %s baseinfo.idpackage FROM baseinfo,provide
WHERE provide.atom = (?) AND
provide.idpackage = baseinfo.idpackage %s %s""" % (
atomstring,slotstring,tagstring,),
searchkeywords
)
if justid:
return self._cur2list(cur)
return cur.fetchall()
def searchPackagesByDescription(self, keyword):
"""
Search packages using given description string as keyword.
@param keyword: description sub-string to search
@type keyword: string
@return: list of tuples of length 2 containing atom and idpackage
values
@rtype: list
"""
cur = self.cursor.execute("""
SELECT baseinfo.atom, baseinfo.idpackage FROM extrainfo, baseinfo
WHERE LOWER(extrainfo.description) LIKE (?) AND
baseinfo.idpackage = extrainfo.idpackage
""", ("%"+keyword.lower()+"%",))
return cur.fetchall()
def searchPackagesByName(self, keyword, sensitive = False, justid = False):
"""
Search packages by package name.
@param keyword: package name to search
@type keyword: string
@keyword sensitive: case sensitive?
@type sensitive: bool
@keyword justid: return list of package identifiers (set()) otherwise
return a list of tuples of length 2 containing atom and idpackage
values
@type justid: bool
@return: list of packages found
@rtype: list or set
"""
atomstring = ''
if not justid:
atomstring = 'atom,'
if sensitive:
cur = self.cursor.execute("""
SELECT %s idpackage FROM baseinfo
WHERE name = (?)
""" % (atomstring,), (keyword,))
else:
cur = self.cursor.execute("""
SELECT %s idpackage FROM baseinfo
WHERE LOWER(name) = (?)
""" % (atomstring,), (keyword.lower(),))
if justid:
return self._cur2list(cur)
return cur.fetchall()
def searchPackagesByCategory(self, keyword, like = False, branch = None):
"""
Search packages by category name.
@param keyword: category name
@type keyword: string
@keyword like: do not match exact case
@type like: bool
@keyword branch: search in given package branch
@type branch: string
@return: list of tuples of length 2 containing atom and idpackage
values
@rtype: list
"""
searchkeywords = [keyword]
branchstring = ''
if branch:
searchkeywords.append(branch)
branchstring = 'and branch = (?)'
if like:
cur = self.cursor.execute("""
SELECT baseinfo.atom,baseinfo.idpackage FROM baseinfo,categories
WHERE categories.category LIKE (?) AND
baseinfo.idcategory = categories.idcategory %s
""" % (branchstring,), searchkeywords)
else:
cur = self.cursor.execute("""
SELECT baseinfo.atom,baseinfo.idpackage FROM baseinfo,categories
WHERE categories.category = (?) AND
baseinfo.idcategory = categories.idcategory %s
""" % (branchstring,), searchkeywords)
return cur.fetchall()
def searchPackagesByNameAndCategory(self, name, category, sensitive = False,
justid = False):
"""
Search packages matching given name and category strings.
@param name: package name to search
@type name: string
@param category: package category to search
@type category: string
@keyword sensitive: case sensitive?
@type sensitive: bool
@keyword justid: return list of package identifiers (set()) otherwise
return a list of tuples of length 2 containing atom and idpackage
values
@type justid: bool
@return: list of packages found
@rtype: list or set
"""
atomstring = ''
if not justid:
atomstring = 'atom,'
if sensitive:
cur = self.cursor.execute("""
SELECT %s idpackage FROM baseinfo
WHERE name = (?) AND
idcategory IN (
SELECT idcategory FROM categories
WHERE category = (?)
)""" % (atomstring,), (name, category,))
else:
cur = self.cursor.execute("""
SELECT %s idpackage FROM baseinfo
WHERE LOWER(name) = (?) AND
idcategory IN (
SELECT idcategory FROM categories
WHERE LOWER(category) = (?)
)""" % (atomstring,), (name.lower(), category.lower(),))
if justid:
return self._cur2list(cur)
return cur.fetchall()
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 (idpackage or -1, idreason or 0,)
@rtype: tuple
"""
searchdata = (atom, slot, revision,)
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo
where atom = (?)
AND slot = (?)
AND revision = (?)""", searchdata)
rslt = cur.fetchone()
if rslt: # check if it's masked
return self.idpackageValidator(rslt[0])
return -1, 0
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
"""
cur = self.cursor.execute("""
SELECT post_migration_md5sum, post_upgrade_md5sum
FROM entropy_branch_migration
WHERE repository = (?) AND from_branch = (?) AND to_branch = (?)
""", (repository, from_branch, to_branch,))
return cur.fetchone()
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 given metadatum, "atom", "slot", "revision"
or "idpackage"
@type order_by: string
@return: list of tuples of length 3 (or 4 if get_scope is True),
containing (atom, idpackage, branch,) if get_scope is False and
(idpackage, atom, slot, revision,) if get_scope is True
@rtype: list
"""
order_txt = ''
if order_by:
order_txt = ' ORDER BY %s' % (order_by,)
if get_scope:
cur = self.cursor.execute("""
SELECT idpackage,atom,slot,revision FROM baseinfo""" + order_txt)
else:
cur = self.cursor.execute("""
SELECT atom,idpackage,branch FROM baseinfo""" + order_txt)
return cur.fetchall()
def listAllInjectedPackages(self, just_files = False):
"""
List all the "injected" package download URLs in repository.
@keyword just_files: just return download URLs
@type just_files: bool
@return: list (set) of download URLs (if just_files) otherwise list
of tuples of length 2 composed by (download URL, idpackage,)
@rtype: set
"""
cur = self.cursor.execute('SELECT idpackage FROM injected')
injecteds = self._cur2set(cur)
results = set()
for injected in injecteds:
download = self.retrieveDownloadURL(injected)
if just_files:
results.add(download)
else:
results.add((download, injected))
return results
def listAllSpmUids(self):
"""
List all Source Package Manager unique package identifiers bindings
with packages in repository.
@return: list of tuples of length 2 composed by (spm_uid, idpackage,)
@rtype: list
"""
cur = self.cursor.execute('SELECT counter, idpackage FROM counters')
return cur.fetchall()
def listAllIdpackages(self, order_by = None):
"""
List all package identifiers available in repository.
@keyword order_by: order by "atom", "idpackage", "version", "name",
"idcategory"
@type order_by: string
@return: list (if order_by) or set of package identifiers
@rtype: list or set
"""
orderbystring = ''
if order_by:
orderbystring = ' ORDER BY '+order_by
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo""" + orderbystring)
try:
if order_by:
return self._cur2list(cur)
return self._cur2set(cur)
except self.dbapi2.OperationalError:
if order_by:
return []
return set()
def listAllDependencies(self):
"""
List all dependencies available in repository.
@return: list of tuples of length 2 containing (iddependency, dependency
name,)
@rtype: list
"""
cur = self.cursor.execute("""
SELECT iddependency, dependency FROM dependenciesreference""")
return cur.fetchall()
def listIdPackagesInIdcategory(self, idcategory, order_by = 'atom'):
"""
List package identifiers available in given category identifier.
@param idcategory: cateogory identifier
@type idcategory: int
@keyword order_by: order by "atom", "name", "version"
@type order_by: string
@return: list (set) of available package identifiers in category.
@rtype: set
"""
order_by_string = ''
if order_by in ("atom", "name", "version",):
order_by_string = ' ORDER BY %s' % (order_by,)
cur = self.cursor.execute("""
SELECT idpackage FROM baseinfo where idcategory = (?)
""" + order_by_string, (idcategory,))
return self._cur2set(cur)
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: list (or set if do_sort is True) of package download URLs
@rtype: list or set
"""
order_string = ''
if do_sort:
order_string = 'ORDER BY extrainfo.download'
cur = self.cursor.execute("""
SELECT extrainfo.download FROM baseinfo, extrainfo
WHERE baseinfo.idpackage = extrainfo.idpackage %s
""" % (order_string,))
if do_sort:
results = self._cur2list(cur)
else:
results = self._cur2set(cur)
if not full_path:
results = [os.path.basename(x) for x in results]
return results
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: list of files available or their count
@rtype: int or list or set
"""
self.connection.text_factory = \
lambda x: unicode(x, "raw_unicode_escape")
if count:
cur = self.cursor.execute('SELECT count(file) FROM content')
else:
cur = self.cursor.execute('SELECT file FROM content')
if count:
return cur.fetchone()[0]
if clean:
return self._cur2set(cur)
return self._cur2list(cur)
def listAllCategories(self, order_by = ''):
"""
List all categories available in repository.
@keyword order_by: order by "category", "idcategory"
@type order_by: string
@return: list of tuples of length 2 composed by (idcategory, category,)
@rtype: list
"""
order_by_string = ''
if order_by: order_by_string = ' order by %s' % (order_by,)
self.cursor.execute('SELECT idcategory,category FROM categories %s' % (
order_by_string,))
return self.cursor.fetchall()
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
"""
mask_t = ''
if mask:
mask_t = 'mask'
cur = self.cursor.execute("""
SELECT protect FROM configprotectreference WHERE idprotect IN
(SELECT distinct(idprotect) FROM configprotect%s)
ORDER BY protect""" % (mask_t,))
results = self._cur2set(cur)
dirs = set()
for mystr in results:
dirs |= set(map(unicode, mystr.split()))
return sorted(dirs)
def switchBranch(self, idpackage, tobranch):
"""
Switch branch string in repository to new value.
@param idpackage: package identifier
@type idpackage: int
@param tobranch: new branch value
@type tobranch: string
"""
with self.__write_mutex:
self.cursor.execute("""
UPDATE baseinfo SET branch = (?)
WHERE idpackage = (?)""", (tobranch, idpackage,))
self.commitChanges()
self.clearCache()
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
"""
if setting_name not in EntropyRepository.SETTING_KEYS:
raise KeyError
try:
self.cursor.execute("""
SELECT setting_value FROM settings WHERE setting_name = (?)
""", (setting_name,))
except self.dbapi2.Error:
raise KeyError
setting = self.cursor.fetchone()
if setting is None:
raise KeyError
return setting[0]
def _setupInitialSettings(self):
"""
Setup initial repository settings
"""
self.cursor.executescript("""
INSERT OR REPLACE INTO settings VALUES ("arch", "%s");
""" % (etpConst['currentarch'],)
)
def _databaseStructureUpdates(self):
old_readonly = self.readOnly
self.readOnly = False
if not self._doesTableExist("licenses_accepted"):
self._createLicensesAcceptedTable()
if not self._doesColumnInTableExist("installedtable", "source"):
self._createInstalledTableSource()
if not self._doesTableExist('packagesets'):
self._createPackagesetsTable()
if not self._doesTableExist('packagechangelogs'):
self._createPackagechangelogsTable()
if not self._doesTableExist('automergefiles'):
self._createAutomergefilesTable()
if not self._doesTableExist('packagesignatures'):
self._createPackagesignaturesTable()
if not self._doesTableExist('packagespmphases'):
self._createPackagespmphases()
if not self._doesTableExist('entropy_branch_migration'):
self._createEntropyBranchMigrationTable()
if not self._doesTableExist('provided_libs'):
self._createProvidedLibs()
if not self._doesTableExist('dependstable'):
self._createDependsTable()
if not self._doesTableExist('settings'):
self._createSettingsTable()
self.readOnly = old_readonly
self.connection.commit()
def validateDatabase(self):
"""
Validates Entropy repository by doing basic integrity checks.
@raise SystemDatabaseError: when repository is not reliable
"""
self.cursor.execute("""
SELECT name FROM SQLITE_MASTER WHERE type = (?) AND name = (?)
""", ("table", "baseinfo"))
rslt = self.cursor.fetchone()
if rslt is None:
mytxt = _("baseinfo error. Either does not exist or corrupted.")
raise SystemDatabaseError("SystemDatabaseError: %s" % (mytxt,))
self.cursor.execute("""
SELECT name FROM SQLITE_MASTER WHERE type = (?) AND name = (?)
""", ("table", "extrainfo"))
rslt = self.cursor.fetchone()
if rslt is None:
mytxt = _("extrainfo error. Either does not exist or corrupted.")
raise SystemDatabaseError("SystemDatabaseError: %s" % (mytxt,))
def getIdpackagesDifferences(self, foreign_idpackages):
"""
Return differences between in-repository package identifiers and
list provided.
@param foreign_idpackages: list of foreign idpackages
@type foreign_idpackages: iterable
@return: tuple composed by idpackages that would be added and idpackages
that would be removed
@rtype: tuple
"""
myids = self.listAllIdpackages()
if isinstance(foreign_idpackages, (list, tuple,)):
outids = set(foreign_idpackages)
else:
outids = foreign_idpackages
added_ids = outids - myids
removed_ids = myids - outids
return added_ids, removed_ids
def uniformBranch(self, branch):
"""
Enforce given branch string to all currently available packages.
@param branch: branch string to enforce
@type branch: string
"""
with self.__write_mutex:
self.cursor.execute('UPDATE baseinfo SET branch = (?)', (branch,))
self.commitChanges()
self.clearCache()
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
"""
added_ids, removed_ids = self.getIdpackagesDifferences(
dbconn.listAllIdpackages())
if not force:
if len(added_ids) > align_limit: # too much hassle
return 0
if len(removed_ids) > align_limit: # too much hassle
return 0
if not added_ids and not removed_ids:
return -1
mytxt = red("%s, %s ...") % (
_("Syncing current database"),
_("please wait"),
)
self.updateProgress(
mytxt,
importance = 1,
type = "info",
header = output_header,
back = True
)
maxcount = len(removed_ids)
mycount = 0
for idpackage in removed_ids:
mycount += 1
mytxt = "%s: %s" % (
red(_("Removing entry")),
blue(str(self.retrieveAtom(idpackage))),
)
self.updateProgress(
mytxt,
importance = 0,
type = "info",
header = output_header,
back = True,
count = (mycount, maxcount)
)
self.removePackage(idpackage, do_cleanup = False, do_commit = False)
maxcount = len(added_ids)
mycount = 0
for idpackage in added_ids:
mycount += 1
mytxt = "%s: %s" % (
red(_("Adding entry")),
blue(str(dbconn.retrieveAtom(idpackage))),
)
self.updateProgress(
mytxt,
importance = 0,
type = "info",
header = output_header,
back = True,
count = (mycount, maxcount)
)
mydata = dbconn.getPackageData(idpackage, get_content = True,
content_insert_formatted = True)
self.addPackage(
mydata,
revision = mydata['revision'],
idpackage = idpackage,
do_commit = False,
formatted_content = True
)
# do some cleanups
self.doCleanups()
# clear caches
self.clearCache()
self.commitChanges()
self.regenerateReverseDependenciesMetadata(verbose = False)
dbconn.clearCache()
# verify both checksums, if they don't match, bomb out
mycheck = self.checksum(do_order = True, strict = False)
outcheck = dbconn.checksum(do_order = True, strict = False)
if mycheck == outcheck:
return 1
return 0
def checkDatabaseApi(self):
"""
Check if repository EAPI (Entropy API) is not greater than the one
that entropy.const ships.
"""
dbapi = self.getApi()
if int(dbapi) > int(etpConst['etpapi']):
self.updateProgress(
red(_("Repository EAPI > Entropy EAPI.")),
importance = 1,
type = "warning",
header = " * ! * ! * ! * "
)
self.updateProgress(
red(_("Please update Equo/Entropy as soon as possible !")),
importance = 1,
type = "warning",
header = " * ! * ! * ! * "
)
def doDatabaseImport(self, 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
@todo: remove /usr/bin/sqlite3 dependency
"""
import subprocess
sqlite3_exec = "/usr/bin/sqlite3 %s < %s" % (dbfile, dumpfile,)
retcode = subprocess.call(sqlite3_exec, shell = True)
return retcode
def doDatabaseExport(self, dumpfile, gentle_with_tables = True,
exclude_tables = None):
"""
Export running SQLite3 database to file.
@param dumpfile: dump file object to write to
@type dumpfile: file object (hint: open())
@keyword gentle_with_tables: append "IF NOT EXISTS" to "CREATE TABLE"
statements
@type gentle_with_tables: bool
@todo: when Python 2.6, look ad Connection.iterdump and replace this :)
"""
if not exclude_tables:
exclude_tables = []
dumpfile.write("BEGIN TRANSACTION;\n")
self.cursor.execute("""
SELECT name, type, sql FROM sqlite_master
WHERE sql NOT NULL AND type=='table'
""")
for name, x, sql in self.cursor.fetchall():
self.updateProgress(
red("%s " % (
_("Exporting database table"),
) ) + "["+blue(str(name))+"]",
importance = 0,
type = "info",
back = True,
header = " "
)
if name == "sqlite_sequence":
dumpfile.write("DELETE FROM sqlite_sequence;\n")
elif name == "sqlite_stat1":
dumpfile.write("ANALYZE sqlite_master;\n")
elif name.startswith("sqlite_"):
continue
else:
t_cmd = "CREATE TABLE"
if sql.startswith(t_cmd) and gentle_with_tables:
sql = "CREATE TABLE IF NOT EXISTS"+sql[len(t_cmd):]
dumpfile.write("%s;\n" % sql)
if name in exclude_tables:
continue
self.cursor.execute("PRAGMA table_info('%s')" % name)
cols = [str(r[1]) for r in self.cursor.fetchall()]
q = "SELECT 'INSERT INTO \"%(tbl_name)s\" VALUES("
q += ", ".join(["'||quote(" + x + ")||'" for x in cols])
q += ")' FROM '%(tbl_name)s'"
self.cursor.execute(q % {'tbl_name': name})
self.connection.text_factory = lambda x: \
unicode(x, "raw_unicode_escape")
for row in self.cursor:
dumpfile.write(
"%s;\n" % str(row[0].encode('raw_unicode_escape')))
self.cursor.execute("""
SELECT name, type, sql FROM sqlite_master
WHERE sql NOT NULL AND type!='table' AND type!='meta'
""")
for name, x, sql in self.cursor.fetchall():
dumpfile.write("%s;\n" % sql)
dumpfile.write("COMMIT;\n")
try:
dumpfile.flush()
except:
pass
self.updateProgress(
red(_("Database Export completed.")),
importance = 0,
type = "info",
header = " "
)
# remember to close the file
def listAllTables(self):
"""
List all available tables in this repository database.
@return: available tables
@rtype: list
"""
cur = self.cursor.execute("""
SELECT name FROM SQLITE_MASTER WHERE type = "table"
""")
return self._cur2list(cur)
def _doesTableExist(self, table):
cur = self.cursor.execute("""
select name from SQLITE_MASTER where type = "table" and name = (?)
""", (table,))
rslt = cur.fetchone()
if rslt is None:
return False
return True
def _doesColumnInTableExist(self, table, column):
cur = self.cursor.execute('PRAGMA table_info( %s )' % (table,))
rslt = (x[1] for x in cur.fetchall())
if column in rslt:
return True
return False
def checksum(self, do_order = False, strict = True,
strings = 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
@return: repository checksum
@rtype: string
"""
c_tup = ("checksum", do_order, strict, strings,)
cache = self.live_cache.get(c_tup)
if cache is not None:
return cache
idpackage_order = ''
category_order = ''
license_order = ''
flags_order = ''
if do_order:
idpackage_order = 'order by idpackage'
category_order = 'order by category'
license_order = 'order by license'
flags_order = 'order by chost'
def do_update_md5(m, cursor):
mydata = cursor.fetchall()
for record in mydata:
for item in record:
m.update(str(item))
if strings:
import hashlib
m = hashlib.md5()
cur = self.cursor.execute("""
SELECT idpackage,atom,name,version,versiontag,
revision,branch,slot,etpapi,trigger FROM
baseinfo %s""" % (idpackage_order,))
if strings:
do_update_md5(m, cur)
else:
a_hash = hash(tuple(cur.fetchall()))
cur = self.cursor.execute("""
SELECT idpackage, description, homepage,
download, size, digest, datecreation FROM
extrainfo %s""" % (idpackage_order,))
if strings:
do_update_md5(m, cur)
else:
b_hash = hash(tuple(cur.fetchall()))
cur = self.cursor.execute("""
SELECT category FROM categories %s
""" % (category_order,))
if strings:
do_update_md5(m, cur)
else:
c_hash = hash(tuple(cur.fetchall()))
d_hash = '0'
e_hash = '0'
if strict:
cur = self.cursor.execute("""
SELECT * FROM licenses %s""" % (license_order,))
if strings:
do_update_md5(m, cur)
else:
d_hash = hash(tuple(cur.fetchall()))
cur = self.cursor.execute('select * from flags %s' % (flags_order,))
if strings:
do_update_md5(m, cur)
else:
e_hash = hash(tuple(cur.fetchall()))
if strings:
result = m.hexdigest()
else:
result = "%s:%s:%s:%s:%s" % (a_hash, b_hash, c_hash, d_hash,
e_hash,)
self.live_cache[c_tup] = result[:]
return result
########################################################
####
## Client Database API / but also used by server part
#
def storeInstalledPackage(self, idpackage, 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 idpackage: package indentifier
@type idpackage: int
@param repoid: repository identifier
@type repoid: string
@param source: source identifier (pleas see:
etpConst['install_sources'])
@type source: int
"""
with self.__write_mutex:
self.cursor.execute('INSERT into installedtable VALUES (?,?,?)',
(idpackage, repoid, source,))
def getInstalledPackageRepository(self, idpackage):
"""
Note: this is used by installed packages repository (also known as
client db).
Return repository identifier stored inside the "installed packages
table".
@param idpackage: package indentifier
@type idpackage: int
@return: repository identifier
@rtype: string or None
"""
with self.__write_mutex:
try:
cur = self.cursor.execute("""
SELECT repositoryname FROM installedtable
WHERE idpackage = (?)""", (idpackage,))
return cur.fetchone()[0]
except (self.dbapi2.OperationalError, TypeError,):
return None
def dropInstalledPackageFromStore(self, idpackage):
"""
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 idpackage: package indentifier
@type idpackage: int
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM installedtable
WHERE idpackage = (?)""", (idpackage,))
def _removePackageFromDependsTable(self, idpackage):
with self.__write_mutex:
try:
self.cursor.execute("""
DELETE FROM dependstable WHERE idpackage = (?)
""", (idpackage,))
return 0
except (self.dbapi2.OperationalError,):
return 1 # need reinit
def _createDependsTable(self):
with self.__write_mutex:
self.cursor.executescript("""
CREATE TABLE IF NOT EXISTS dependstable
( iddependency INTEGER PRIMARY KEY, idpackage INTEGER );
INSERT INTO dependstable VALUES (-1,-1);
""")
if self.indexing:
self.cursor.execute("""
CREATE INDEX IF NOT EXISTS dependsindex_idpackage
ON dependstable ( idpackage )
""")
self.commitChanges()
def _sanitizeDependsTable(self):
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM dependstable where iddependency = -1
""")
self.commitChanges()
def _isDependsTableSane(self):
try:
cur = self.cursor.execute("""
SELECT iddependency FROM dependstable WHERE iddependency = -1
""")
except (self.dbapi2.OperationalError,):
return False # table does not exist, please regenerate and re-run
status = cur.fetchone()
if status:
return False
cur = self.cursor.execute("SELECT count(*) FROM dependstable")
dependstable_count = cur.fetchone()
if dependstable_count < 2:
return False
return True
def storeXpakMetadata(self, idpackage, blob):
"""
Xpak metadata is Source Package Manager package metadata.
This method stores such metadata inside repository.
@param idpackage: package indentifier
@type idpackage: int
@param blob: metadata blob
@type blob: string or buffer
"""
with self.__write_mutex:
self.cursor.execute('INSERT into xpakdata VALUES (?,?)',
(int(idpackage), buffer(blob),)
)
self.commitChanges()
def retrieveXpakMetadata(self, idpackage):
"""
Xpak metadata is Source Package Manager package metadata.
This method returns such stored metadata inside repository.
@param idpackage: package indentifier
@type idpackage: int
@return: stored metadata
@rtype: buffer
"""
try:
cur = self.cursor.execute("""
SELECT data from xpakdata where idpackage = (?)
""", (idpackage,))
mydata = cur.fetchone()
if not mydata:
return ""
return mydata[0]
except (self.dbapi2.Error, TypeError, IndexError,):
return ""
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
"""
if not self._doesTableExist('entropy_branch_migration'):
return {}
cur = self.cursor.execute("""
SELECT repository, from_branch, post_migration_md5sum,
post_upgrade_md5sum FROM entropy_branch_migration WHERE to_branch = (?)
""", (to_branch,))
data = cur.fetchall()
meta = {}
for repo, from_branch, post_migration_md5, post_upgrade_md5 in data:
obj = meta.setdefault(repo, {})
obj[from_branch] = (post_migration_md5, post_upgrade_md5,)
return meta
def dropContent(self):
"""
Drop all "content" metadata from repository, usually a memory hog.
Content metadata contains files and directories owned by packages.
"""
with self.__write_mutex:
self.cursor.execute('DELETE FROM content')
def dropAllIndexes(self):
"""
Drop all repository metadata indexes.
"""
cur = self.cursor.execute("""
SELECT name FROM SQLITE_MASTER WHERE type = "index"
""")
indexes = self._cur2set(cur)
with self.__write_mutex:
for index in indexes:
try:
self.cursor.execute('DROP INDEX IF EXISTS %s' % (index,))
except self.dbapi2.Error:
continue
def listAllIndexes(self, only_entropy = True):
"""
List all the available repository metadata index names.
@keyword only_entropy: if True, return only entropy related indexes
@type only_entropy: bool
@return: list (set) of index names
@rtype: set
"""
cur = self.cursor.execute("""
SELECT name FROM SQLITE_MASTER WHERE type = "index"
""")
indexes = self._cur2set(cur)
if not only_entropy:
return indexes
return set([x for x in indexes if not x.startswith("sqlite")])
def createAllIndexes(self):
"""
Create all the repository metadata indexes internally available.
"""
self._createMirrorlinksIndex()
self._createContentIndex()
self._createBaseinfoIndex()
self._createKeywordsIndex()
self._createDependenciesIndex()
self._createProvideIndex()
self._createConflictsIndex()
self._createExtrainfoIndex()
self._createNeededIndex()
self._createUseflagsIndex()
self._createLicensedataIndex()
self._createLicensesIndex()
self._createConfigProtectReferenceIndex()
self._createMessagesIndex()
self._createSourcesIndex()
self._createCountersIndex()
self._createEclassesIndex()
self._createCategoriesIndex()
self._createCompileFlagsIndex()
self._createPackagesetsIndex()
self._createAutomergefilesIndex()
self._createProvidedLibsIndex()
def _createMirrorlinksIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.execute("""
CREATE INDEX IF NOT EXISTS mirrorlinks_mirrorname
ON mirrorlinks ( mirrorname )""")
def _createPackagesetsIndex(self):
if self.indexing:
with self.__write_mutex:
try:
self.cursor.execute("""
CREATE INDEX IF NOT EXISTS packagesetsindex
ON packagesets ( setname )""")
except self.dbapi2.OperationalError:
pass
def _createProvidedLibsIndex(self):
if self.indexing:
with self.__write_mutex:
try:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS provided_libs_library
ON provided_libs ( library );
CREATE INDEX IF NOT EXISTS provided_libs_idpackage
ON provided_libs ( idpackage );
CREATE INDEX IF NOT EXISTS provided_libs_lib_elf
ON provided_libs ( library, elfclass );
""")
except self.dbapi2.OperationalError:
pass
def _createAutomergefilesIndex(self):
if self.indexing:
with self.__write_mutex:
try:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS automergefiles_idpackage
ON automergefiles ( idpackage );
CREATE INDEX IF NOT EXISTS automergefiles_file_md5
ON automergefiles ( configfile, md5 );
""")
except self.dbapi2.OperationalError:
pass
def _createNeededIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS neededindex ON neededreference
( library );
CREATE INDEX IF NOT EXISTS neededindex_idneeded ON needed
( idneeded );
CREATE INDEX IF NOT EXISTS neededindex_idpackage ON needed
( idpackage );
CREATE INDEX IF NOT EXISTS neededindex_elfclass ON needed
( elfclass );
""")
def _createMessagesIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.execute("""
CREATE INDEX IF NOT EXISTS messagesindex ON messages
( idpackage )
""")
def _createCompileFlagsIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.execute("""
CREATE INDEX IF NOT EXISTS flagsindex ON flags
( chost, cflags, cxxflags )
""")
def _createUseflagsIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS useflagsindex_useflags_idpackage
ON useflags ( idpackage );
CREATE INDEX IF NOT EXISTS useflagsindex_useflags_idflag
ON useflags ( idflag );
CREATE INDEX IF NOT EXISTS useflagsindex
ON useflagsreference ( flagname );
""")
def _createContentIndex(self):
if self.indexing:
with self.__write_mutex:
if self._doesTableExist("content"):
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS contentindex_couple
ON content ( idpackage );
CREATE INDEX IF NOT EXISTS contentindex_file
ON content ( file );
""")
def _createConfigProtectReferenceIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.execute("""
CREATE INDEX IF NOT EXISTS configprotectreferenceindex
ON configprotectreference ( protect )
""")
def _createBaseinfoIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS baseindex_atom
ON baseinfo ( atom );
CREATE INDEX IF NOT EXISTS baseindex_branch_name
ON baseinfo ( name,branch );
CREATE INDEX IF NOT EXISTS baseindex_branch_name_idcategory
ON baseinfo ( name,idcategory,branch );
CREATE INDEX IF NOT EXISTS baseindex_idcategory
ON baseinfo ( idcategory );
""")
def _createLicensedataIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.execute("""
CREATE INDEX IF NOT EXISTS licensedataindex
ON licensedata ( licensename )
""")
def _createLicensesIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.execute("""
CREATE INDEX IF NOT EXISTS licensesindex ON licenses ( license )
""")
def _createCategoriesIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.execute("""
CREATE INDEX IF NOT EXISTS categoriesindex_category
ON categories ( category )
""")
def _createKeywordsIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS keywordsreferenceindex
ON keywordsreference ( keywordname );
CREATE INDEX IF NOT EXISTS keywordsindex_idpackage
ON keywords ( idpackage );
CREATE INDEX IF NOT EXISTS keywordsindex_idkeyword
ON keywords ( idkeyword );
""")
def _createDependenciesIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS dependenciesindex_idpackage
ON dependencies ( idpackage );
CREATE INDEX IF NOT EXISTS dependenciesindex_iddependency
ON dependencies ( iddependency );
CREATE INDEX IF NOT EXISTS dependenciesreferenceindex_dependency
ON dependenciesreference ( dependency );
""")
def _createCountersIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS countersindex_idpackage
ON counters ( idpackage );
CREATE INDEX IF NOT EXISTS countersindex_counter
ON counters ( counter );
""")
def _createSourcesIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS sourcesindex_idpackage
ON sources ( idpackage );
CREATE INDEX IF NOT EXISTS sourcesindex_idsource
ON sources ( idsource );
CREATE INDEX IF NOT EXISTS sourcesreferenceindex_source
ON sourcesreference ( source );
""")
def _createProvideIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS provideindex_idpackage
ON provide ( idpackage );
CREATE INDEX IF NOT EXISTS provideindex_atom
ON provide ( atom );
""")
def _createConflictsIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS conflictsindex_idpackage
ON conflicts ( idpackage );
CREATE INDEX IF NOT EXISTS conflictsindex_atom
ON conflicts ( conflict );
""")
def _createExtrainfoIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS extrainfoindex
ON extrainfo ( description );
CREATE INDEX IF NOT EXISTS extrainfoindex_pkgindex
ON extrainfo ( idpackage );
""")
def _createEclassesIndex(self):
if self.indexing:
with self.__write_mutex:
self.cursor.executescript("""
CREATE INDEX IF NOT EXISTS eclassesindex_idpackage
ON eclasses ( idpackage );
CREATE INDEX IF NOT EXISTS eclassesindex_idclass
ON eclasses ( idclass );
CREATE INDEX IF NOT EXISTS eclassesreferenceindex_classname
ON eclassesreference ( classname );
""")
def dropContentIndex(self, only_file = False):
"""
Drop "content" metadata index.
@keyword only_file: drop only "file" index
@type only_file: bool
"""
with self.__write_mutex:
self.cursor.execute("DROP INDEX IF EXISTS contentindex_file")
if not only_file:
self.cursor.executescript("""
DROP INDEX IF EXISTS contentindex_couple;
""")
def regenerateSpmUidTable(self, verbose = False):
"""
Regenerate Source Package Manager package identifiers table.
This method will use the Source Package Manger interface.
@keyword verbose: run in verbose mode
@type verbose: bool
"""
# Poll SPM, load variables
spm = get_spm(self)
# this is necessary now, counters table should be empty
with self.__write_mutex:
self.cursor.executescript("""
DROP TABLE IF EXISTS counters_regen;
CREATE TEMPORARY TABLE counters_regen (
counter INTEGER,
idpackage INTEGER,
branch VARCHAR,
PRIMARY KEY(idpackage, branch)
);
""")
# assign a counter to an idpackage
counter_path = etpConst['spm']['xpak_entries']['counter']
for myid in self.listAllIdpackages():
# get atom
myatom = self.retrieveAtom(myid)
mybranch = self.retrieveBranch(myid)
myatom = self.entropyTools.remove_tag(myatom)
build_path = spm.get_installed_package_build_script_path(myatom)
myatomcounterpath = os.path.join(os.path.dirname(build_path),
counter_path)
if not (os.access(myatomcounterpath, os.R_OK) and \
os.path.isfile(myatomcounterpath)):
if verbose:
mytxt = "%s: %s: %s" % (
bold(_("ATTENTION")),
red(_("Spm counter path not found in")),
bold(myatomcounterpath),
)
self.updateProgress(
mytxt,
importance = 1,
type = "warning"
)
continue
try:
with open(myatomcounterpath, "r") as f:
counter = int(f.readline().strip())
except ValueError:
# counter is not int, and fucked up
if verbose:
mytxt = "%s: %s: %s" % (
bold(_("ATTENTION")),
red(_("Spm id is not valid for")),
bold(myatom),
)
self.updateProgress(
mytxt,
importance = 1,
type = "warning"
)
continue
except Exception, e:
if verbose:
mytxt = "%s: %s: %s [%s]" % (
bold(_("ATTENTION")),
red(_("cannot open Spm id file for")),
bold(myatom),
e,
)
self.updateProgress(
mytxt,
importance = 1,
type = "warning"
)
continue
# insert id+counter
try:
self.cursor.execute("""
INSERT into counters_regen VALUES (?,?,?)
""", (counter, myid, mybranch,))
except self.dbapi2.IntegrityError:
if verbose:
mytxt = "%s: %s: %s" % (
bold(_("ATTENTION")),
red(_("id for atom is duplicated, ignoring")),
bold(myatom),
)
self.updateProgress(
mytxt,
importance = 1,
type = "warning"
)
continue
# don't trust counters, they might not be unique
self.cursor.executescript("""
DELETE FROM counters;
INSERT INTO counters (counter, idpackage, branch)
SELECT counter, idpackage, branch FROM counters_regen;
""")
self.commitChanges()
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
"""
with self.__write_mutex:
self.cursor.execute("""
DELETE FROM treeupdates WHERE repository = (?)
""", (repository,))
self.commitChanges()
def resetTreeupdatesDigests(self):
"""
This method should be considered internal and not suited for general
audience. Reset "treeupdates" digest metadata.
"""
with self.__write_mutex:
self.cursor.execute('UPDATE treeupdates SET digest = "-1"')
self.commitChanges()
def _migrateCountersTable(self):
self.cursor.executescript("""
DROP TABLE IF EXISTS counterstemp;
CREATE TABLE counterstemp (
counter INTEGER, idpackage INTEGER, branch VARCHAR,
PRIMARY KEY(idpackage,branch)
);
INSERT INTO counterstemp (counter, idpackage, branch)
SELECT counter, idpackage, branch FROM counters;
DROP TABLE counters;
ALTER TABLE counterstemp RENAME TO counters;
""")
self.commitChanges()
def _createSettingsTable(self):
with self.__write_mutex:
self.cursor.executescript("""
CREATE TABLE settings (
setting_name VARCHAR,
setting_value VARCHAR,
PRIMARY KEY(setting_name)
);
""")
self._setupInitialSettings()
def _createProvidedLibs(self):
def do_create():
self.cursor.executescript("""
CREATE TABLE provided_libs (
idpackage INTEGER,
library VARCHAR,
path VARCHAR,
elfclass INTEGER
);
""")
with self.__write_mutex:
if self.clientDatabase and (self.dbname != etpConst['clientdbid']):
return do_create()
mytxt = "%s: %s" % (
bold(_("ATTENTION")),
red(_("generating provided_libs metadata, please wait!")),
)
self.updateProgress(
mytxt,
importance = 1,
type = "warning"
)
try:
self._generateProvidedLibsMetadata()
except (IOError, OSError, self.dbapi2.Error), err:
mytxt = "%s: %s: [%s]" % (
bold(_("ATTENTION")),
red("cannot generate provided_libs metadata"),
err,
)
self.updateProgress(
mytxt,
importance = 1,
type = "warning"
)
do_create()
def _generateProvidedLibsMetadata(self):
def collect_provided(pkg_dir, content):
provided_libs = set()
ldpaths = self.entropyTools.collect_linker_paths()
for obj, ftype in content.items():
if ftype == "dir":
continue
obj_dir, obj_name = os.path.split(obj)
if obj_dir not in ldpaths:
continue
unpack_obj = os.path.join(pkg_dir, obj)
try:
os.stat(unpack_obj)
except OSError:
continue
# do not trust ftype
if os.path.isdir(unpack_obj):
continue
if not self.entropyTools.is_elf_file(unpack_obj):
continue
elf_class = self.entropyTools.read_elf_class(unpack_obj)
provided_libs.add((obj_name, obj, elf_class,))
return provided_libs
self.cursor.executescript("""
DROP TABLE IF EXISTS provided_libs_tmp;
CREATE TABLE provided_libs_tmp (
idpackage INTEGER,
library VARCHAR,
path VARCHAR,
elfclass INTEGER
);
""")
pkgs = self.listAllIdpackages()
for idpackage in pkgs:
content = self.retrieveContent(idpackage, extended = True,
formatted = True)
provided_libs = collect_provided(etpConst['systemroot'], content)
self.cursor.executemany("""
INSERT INTO provided_libs_tmp VALUES (?,?,?,?)
""", [(idpackage, x, y, z,) for x, y, z in provided_libs])
# rename
self.cursor.execute("""
ALTER TABLE provided_libs_tmp RENAME TO provided_libs;
""")
def _createInstalledTableSource(self):
with self.__write_mutex:
self.cursor.execute("""
ALTER TABLE installedtable ADD source INTEGER;
""")
self.cursor.execute("""
UPDATE installedtable SET source = (?)
""", (etpConst['install_sources']['unknown'],))
def _createPackagechangelogsTable(self):
with self.__write_mutex:
self.cursor.execute("""
CREATE TABLE packagechangelogs ( category VARCHAR,
name VARCHAR, changelog BLOB, PRIMARY KEY (category, name));
""")
def _createAutomergefilesTable(self):
with self.__write_mutex:
self.cursor.execute("""
CREATE TABLE automergefiles ( idpackage INTEGER,
configfile VARCHAR, md5 VARCHAR );
""")
def _createPackagesignaturesTable(self):
with self.__write_mutex:
self.cursor.execute("""
CREATE TABLE packagesignatures (
idpackage INTEGER PRIMARY KEY,
sha1 VARCHAR,
sha256 VARCHAR,
sha512 VARCHAR );
""")
def _createPackagespmphases(self):
with self.__write_mutex:
self.cursor.execute("""
CREATE TABLE packagespmphases (
idpackage INTEGER PRIMARY KEY,
phases VARCHAR
);
""")
def _createEntropyBranchMigrationTable(self):
with self.__write_mutex:
self.cursor.execute("""
CREATE TABLE entropy_branch_migration (
repository VARCHAR,
from_branch VARCHAR,
to_branch VARCHAR,
post_migration_md5sum VARCHAR,
post_upgrade_md5sum VARCHAR,
PRIMARY KEY (repository, from_branch, to_branch)
);
""")
def _createPackagesetsTable(self):
with self.__write_mutex:
self.cursor.execute("""
CREATE TABLE packagesets ( setname VARCHAR, dependency VARCHAR );
""")
def createCategoriesdescriptionTable(self):
with self.__write_mutex:
self.cursor.execute("""
CREATE TABLE categoriesdescription ( category VARCHAR,
locale VARCHAR, description VARCHAR );
""")
def createLicensedataTable(self):
with self.__write_mutex:
self.cursor.execute("""
CREATE TABLE licensedata ( licensename VARCHAR UNIQUE,
text BLOB, compressed INTEGER );
""")
def _createLicensesAcceptedTable(self):
with self.__write_mutex:
self.cursor.execute("""
CREATE TABLE licenses_accepted ( licensename VARCHAR UNIQUE );
""")
def _addDependsRelationToDependsTable(self, iterable):
with self.__write_mutex:
self.cursor.executemany('INSERT into dependstable VALUES (?,?)',
iterable)
if (self.entropyTools.is_user_in_entropy_group()) and \
(self.dbname.startswith(etpConst['serverdbid'])):
# force commit even if readonly, this will allow
# to automagically fix dependstable server side
# we don't care much about syncing the
# database since it's quite trivial
self.connection.commit()
def taintReverseDependenciesMetadata(self):
"""
Taint reverse (or inverse) dependencies metadata so that will be
generated during the next request.
"""
# FIXME: backward compatibility
if not self._doesTableExist("dependstable"):
return
self.cursor.executescript("""
DELETE FROM dependstable;
INSERT INTO dependstable VALUES (-1,-1);
""")
def regenerateReverseDependenciesMetadata(self, verbose = True):
"""
Regenerate reverse (or inverse) dependencies metadata.
@keyword verbose: enable verbosity
@type verbose: bool
"""
depends = self.listAllDependencies()
count = 0
total = len(depends)
mydata = set()
am = self.atomMatch
up = self.updateProgress
self.taintReverseDependenciesMetadata()
self.commitChanges()
for iddep, atom in depends:
count += 1
if verbose and ((count == 0) or (count % 150 == 0) or \
(count == total)):
up( red("Resolving %s") % (atom,), importance = 0,
type = "info", back = True, count = (count, total)
)
idpackage, rc = am(atom)
if idpackage == -1:
continue
if iddep == -1:
continue
mydata.add((iddep, idpackage,))
if mydata:
try:
self._addDependsRelationToDependsTable(mydata)
except self.dbapi2.IntegrityError:
# try to cope for the last time
self.taintReverseDependenciesMetadata()
self.commitChanges()
self._addDependsRelationToDependsTable(mydata)
# now validate dependstable
self._sanitizeDependsTable()
def moveSpmUidsToBranch(self, to_branch, from_branch = None):
"""
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:
@type to_branch:
@keyword from_branch:
@type from_branch:
@return:
@rtype:
"""
with self.__write_mutex:
if from_branch is not None:
self.cursor.execute("""
UPDATE counters SET branch = (?) WHERE branch = (?)
""", (to_branch, from_branch,))
else:
self.cursor.execute("""
UPDATE counters SET branch = (?)
""", (to_branch,))
self.commitChanges()
self.clearCache()
def __atomMatchFetchCache(self, *args):
if self.xcache:
cached = self.dumpTools.loadobj("%s/%s/%s" % (
self.dbMatchCacheKey, self.dbname, hash(tuple(args)),))
if cached != None: return cached
def __atomMatchStoreCache(self, *args, **kwargs):
if self.xcache:
self.Cacher.push("%s/%s/%s" % (
self.dbMatchCacheKey, self.dbname, hash(tuple(args)),),
kwargs.get('result')
)
def __atomMatchValidateCache(self, cached_obj, multiMatch, extendedResults):
# time wasted for a reason
data, rc = cached_obj
if rc != 0: return cached_obj
if (not extendedResults) and (not multiMatch):
if not self.isIdpackageAvailable(data):
return None
elif extendedResults and (not multiMatch):
if not self.isIdpackageAvailable(data[0]):
return None
elif extendedResults and multiMatch:
idpackages = set([x[0] for x in data])
if not self.areIdpackagesAvailable(idpackages):
return None
elif (not extendedResults) and multiMatch:
# (set([x[0] for x in dbpkginfo]),0)
idpackages = set(data)
if not self.areIdpackagesAvailable(idpackages):
return None
return cached_obj
def _idpackageValidator_live(self, idpackage, reponame):
ref = self.SystemSettings['pkg_masking_reference']
if (idpackage, reponame) in \
self.SystemSettings['live_packagemasking']['mask_matches']:
# do not cache this
return -1, ref['user_live_mask']
elif (idpackage, reponame) in \
self.SystemSettings['live_packagemasking']['unmask_matches']:
return idpackage, ref['user_live_unmask']
def _idpackageValidator_user_package_mask(self, idpackage, reponame, live):
mykw = "%smask_ids" % (reponame,)
user_package_mask_ids = self.SystemSettings.get(mykw)
if not isinstance(user_package_mask_ids, (list, set,)):
user_package_mask_ids = set()
for atom in self.SystemSettings['mask']:
matches, r = self.atomMatch(atom, multiMatch = True,
packagesFilter = False)
if r != 0:
continue
user_package_mask_ids |= set(matches)
self.SystemSettings[mykw] = user_package_mask_ids
if idpackage in user_package_mask_ids:
# sorry, masked
ref = self.SystemSettings['pkg_masking_reference']
myr = ref['user_package_mask']
try:
cl_data = self.SystemSettings[self.client_settings_plugin_id]
validator_cache = cl_data['masking_validation']['cache']
validator_cache[(idpackage, reponame, live)] = -1, myr
except KeyError: # system settings client plugin not found
pass
return -1, myr
def _idpackageValidator_user_package_unmask(self, idpackage, reponame,
live):
# see if we can unmask by just lookin into user
# package.unmask stuff -> self.SystemSettings['unmask']
mykw = "%sunmask_ids" % (reponame,)
user_package_unmask_ids = self.SystemSettings.get(mykw)
if not isinstance(user_package_unmask_ids, (list, set,)):
user_package_unmask_ids = set()
for atom in self.SystemSettings['unmask']:
matches, r = self.atomMatch(atom, multiMatch = True,
packagesFilter = False)
if r != 0:
continue
user_package_unmask_ids |= set(matches)
self.SystemSettings[mykw] = user_package_unmask_ids
if idpackage in user_package_unmask_ids:
ref = self.SystemSettings['pkg_masking_reference']
myr = ref['user_package_unmask']
try:
cl_data = self.SystemSettings[self.client_settings_plugin_id]
validator_cache = cl_data['masking_validation']['cache']
validator_cache[(idpackage, reponame, live)] = idpackage, myr
except KeyError: # system settings client plugin not found
pass
return idpackage, myr
def _idpackageValidator_packages_db_mask(self, idpackage, reponame, live):
# check if repository packages.db.mask needs it masked
repos_mask = {}
client_plg_id = etpConst['system_settings_plugins_ids']['client_plugin']
client_settings = self.SystemSettings.get(client_plg_id, {})
if client_settings:
repos_mask = client_settings['repositories']['mask']
repomask = repos_mask.get(reponame)
if isinstance(repomask, (list, set,)):
# first, seek into generic masking, all branches
# (below) avoid issues with repository names
mask_repo_id = "%s_ids@@:of:%s" % (reponame, reponame,)
repomask_ids = repos_mask.get(mask_repo_id)
if not isinstance(repomask_ids, set):
repomask_ids = set()
for atom in repomask:
matches, r = self.atomMatch(atom, multiMatch = True,
packagesFilter = False)
if r != 0:
continue
repomask_ids |= set(matches)
repos_mask[mask_repo_id] = repomask_ids
if idpackage in repomask_ids:
ref = self.SystemSettings['pkg_masking_reference']
myr = ref['repository_packages_db_mask']
try:
plg_id = self.client_settings_plugin_id
cl_data = self.SystemSettings[plg_id]
validator_cache = cl_data['masking_validation']['cache']
validator_cache[(idpackage, reponame, live)] = -1, myr
except KeyError: # system settings client plugin not found
pass
return -1, myr
def _idpackageValidator_package_license_mask(self, idpackage, reponame,
live):
if not self.SystemSettings['license_mask']:
return
mylicenses = self.retrieveLicense(idpackage)
mylicenses = mylicenses.strip().split()
lic_mask = self.SystemSettings['license_mask']
for mylicense in mylicenses:
if mylicense not in lic_mask:
continue
ref = self.SystemSettings['pkg_masking_reference']
myr = ref['user_license_mask']
try:
cl_data = self.SystemSettings[self.client_settings_plugin_id]
validator_cache = cl_data['masking_validation']['cache']
validator_cache[(idpackage, reponame, live)] = -1, myr
except KeyError: # system settings client plugin not found
pass
return -1, myr
def _idpackageValidator_keyword_mask(self, idpackage, reponame, live):
# WORKAROUND for buggy entries
# ** is fine then
mykeywords = self.retrieveKeywords(idpackage) or set([''])
mask_ref = self.SystemSettings['pkg_masking_reference']
# firstly, check if package keywords are in etpConst['keywords']
# (universal keywords have been merged from package.keywords)
same_keywords = etpConst['keywords'] & mykeywords
if same_keywords:
myr = mask_ref['system_keyword']
try:
cl_data = self.SystemSettings[self.client_settings_plugin_id]
validator_cache = cl_data['masking_validation']['cache']
validator_cache[(idpackage, reponame, live)] = idpackage, myr
except KeyError: # system settings client plugin not found
pass
return idpackage, myr
# if we get here, it means we didn't find mykeywords
# in etpConst['keywords']
# we need to seek self.SystemSettings['keywords']
# seek in repository first
keyword_repo = self.SystemSettings['keywords']['repositories']
for keyword in keyword_repo.get(reponame, {}).keys():
if keyword not in mykeywords:
continue
keyword_data = keyword_repo[reponame].get(keyword)
if not keyword_data:
continue
if "*" in keyword_data:
# all packages in this repo with keyword "keyword" are ok
myr = mask_ref['user_repo_package_keywords_all']
try:
plg_id = self.client_settings_plugin_id
cl_data = self.SystemSettings[plg_id]
validator_cache = cl_data['masking_validation']['cache']
validator_cache[(idpackage, reponame, live)] = \
idpackage, myr
except KeyError: # system settings client plugin not found
pass
return idpackage, myr
kwd_key = "%s_ids" % (keyword,)
keyword_data_ids = keyword_repo[reponame].get(kwd_key)
if not isinstance(keyword_data_ids, set):
keyword_data_ids = set()
for atom in keyword_data:
matches, r = self.atomMatch(atom, multiMatch = True,
packagesFilter = False)
if r != 0:
continue
keyword_data_ids |= matches
keyword_repo[reponame][kwd_key] = keyword_data_ids
if idpackage in keyword_data_ids:
myr = mask_ref['user_repo_package_keywords']
try:
plg_id = self.client_settings_plugin_id
cl_data = self.SystemSettings[plg_id]
validator_cache = cl_data['masking_validation']['cache']
validator_cache[(idpackage, reponame, live)] = \
idpackage, myr
except KeyError: # system settings client plugin not found
pass
return idpackage, myr
keyword_pkg = self.SystemSettings['keywords']['packages']
# if we get here, it means we didn't find a match in repositories
# so we scan packages, last chance
for keyword in keyword_pkg.keys():
# use .keys() because keyword_pkg gets modified during iteration
# first of all check if keyword is in mykeywords
if keyword not in mykeywords:
continue
keyword_data = keyword_pkg.get(keyword)
if not keyword_data:
continue
kwd_key = "%s_ids" % (keyword,)
keyword_data_ids = keyword_pkg.get(reponame+kwd_key)
if not isinstance(keyword_data_ids, (list, set,)):
keyword_data_ids = set()
for atom in keyword_data:
# match atom
matches, r = self.atomMatch(atom, multiMatch = True,
packagesFilter = False)
if r != 0:
continue
keyword_data_ids |= matches
keyword_pkg[reponame+kwd_key] = keyword_data_ids
if idpackage in keyword_data_ids:
# valid!
myr = mask_ref['user_package_keywords']
try:
plg_id = self.client_settings_plugin_id
cl_data = self.SystemSettings[plg_id]
validator_cache = cl_data['masking_validation']['cache']
validator_cache[(idpackage, reponame, live)] = \
idpackage, myr
except KeyError: # system settings client plugin not found
pass
return idpackage, myr
## if we get here, it means that pkg it keyword masked
## and we should look at the very last resort, per-repository
## package keywords
# check if repository contains keyword unmasking data
plg_id = self.client_settings_plugin_id
cl_data = self.SystemSettings.get(plg_id)
if cl_data is None:
# SystemSettings Entropy Client plugin not available
return
# let's see if something is available in repository config
repo_keywords = cl_data['repositories']['repos_keywords'].get(reponame)
if repo_keywords is None:
# nopers, sorry!
return
# check universal keywords
same_keywords = repo_keywords.get('universal') & mykeywords
if same_keywords:
# universal keyword matches!
myr = mask_ref['repository_packages_db_keywords']
validator_cache = cl_data['masking_validation']['cache']
validator_cache[(idpackage, reponame, live)] = \
idpackage, myr
return idpackage, myr
## if we get here, it means that even universal masking failed
## and we need to look at per-package settings
repo_settings = repo_keywords.get('packages')
if not repo_settings:
# it's empty, not worth checking
return
cached_key = "packages_ids"
keyword_data_ids = repo_keywords.get(cached_key)
if not isinstance(keyword_data_ids, dict):
# create cache
keyword_data_ids = {}
for atom, values in repo_settings.items():
matches, r = self.atomMatch(atom, multiMatch = True,
packagesFilter = False)
if r != 0:
continue
for match in matches:
obj = keyword_data_ids.setdefault(match, set())
obj.update(values)
repo_keywords[cached_key] = keyword_data_ids
same_keywords = keyword_data_ids.get(idpackage, set()) & \
etpConst['keywords']
if same_keywords:
# found! this pkg is not masked, yay!
myr = mask_ref['repository_packages_db_keywords']
validator_cache = cl_data['masking_validation']['cache']
validator_cache[(idpackage, reponame, live)] = \
idpackage, myr
return idpackage, myr
def idpackageValidator(self, idpackage, live = True):
"""
Return whether given package identifier is available to user or not,
reading package masking metadata stored in SystemSettings.
@param idpackage: package indentifier
@type idpackage: int
@keyword live: use live masking feature
@type live: bool
@return: tuple composed by idpackage and masking reason. If idpackage
returned idpackage 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
"""
if self.dbname == etpConst['clientdbid']:
return idpackage, 0
elif self.dbname.startswith(etpConst['serverdbid']):
return idpackage, 0
reponame = self.dbname[len(etpConst['dbnamerepoprefix']):]
try:
cl_data = self.SystemSettings[self.client_settings_plugin_id]
validator_cache = cl_data['masking_validation']['cache']
cached = validator_cache.get((idpackage, reponame, live))
if cached != None:
return cached
# avoid memleaks
if len(validator_cache) > 10000:
validator_cache.clear()
except KeyError: # plugin does not exist
pass
if live:
data = self._idpackageValidator_live(idpackage, reponame)
if data:
return data
data = self._idpackageValidator_user_package_mask(idpackage,
reponame, live)
if data:
return data
data = self._idpackageValidator_user_package_unmask(idpackage,
reponame, live)
if data:
return data
data = self._idpackageValidator_packages_db_mask(idpackage, reponame,
live)
if data:
return data
data = self._idpackageValidator_package_license_mask(idpackage,
reponame, live)
if data:
return data
data = self._idpackageValidator_keyword_mask(idpackage, reponame, live)
if data:
return data
# holy crap, can't validate
myr = self.SystemSettings['pkg_masking_reference']['completely_masked']
validator_cache[(idpackage, reponame, live)] = -1, myr
return -1, myr
def _packagesFilter(self, results):
"""
Packages filter used by atomMatch, input must me foundIDs,
a list like this: [608, 1867].
"""
# keywordsFilter ONLY FILTERS results if
# self.dbname.startswith(etpConst['dbnamerepoprefix'])
# => repository database is open
if not self.dbname.startswith(etpConst['dbnamerepoprefix']):
return results
newresults = set()
for idpackage in results:
idpackage, reason = self.idpackageValidator(idpackage)
if idpackage == -1:
continue
newresults.add(idpackage)
return newresults
def __filterSlot(self, idpackage, slot):
if slot is None:
return idpackage
dbslot = self.retrieveSlot(idpackage)
if dbslot == slot:
return idpackage
def __filterTag(self, idpackage, tag, operators):
if tag is None:
return idpackage
dbtag = self.retrieveVersionTag(idpackage)
compare = cmp(tag, dbtag)
if not operators or operators == "=":
if compare == 0:
return idpackage
else:
return self.__do_operator_compare(idpackage, operators, compare)
def __filterUse(self, idpackage, use):
if not use:
return idpackage
pkguse = self.retrieveUseflags(idpackage)
disabled = set([x[1:] for x in use if x.startswith("-")])
enabled = set([x for x in use if not x.startswith("-")])
enabled_not_satisfied = enabled - pkguse
# check enabled
if enabled_not_satisfied:
return None
# check disabled
disabled_not_satisfied = disabled - pkguse
if len(disabled_not_satisfied) != len(disabled):
return None
return idpackage
def __do_operator_compare(self, token, operators, compare):
if operators == ">" and compare == -1:
return token
elif operators == ">=" and compare < 1:
return token
elif operators == "<" and compare == 1:
return token
elif operators == "<=" and compare > -1:
return token
def __filterSlotTagUse(self, foundIDs, slot, tag, use, operators):
def myfilter(idpackage):
idpackage = self.__filterSlot(idpackage, slot)
if not idpackage:
return False
idpackage = self.__filterUse(idpackage, use)
if not idpackage:
return False
idpackage = self.__filterTag(idpackage, tag, operators)
if not idpackage:
return False
return True
return set(filter(myfilter, foundIDs))
def atomMatch(self, atom, caseSensitive = True, matchSlot = None,
multiMatch = False, matchTag = None, matchUse = (),
packagesFilter = True, matchRevision = None, 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: string
@keyword caseSensitive: match in case sensitive mode
@type caseSensitive: bool
@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 matchTag: match packages with given tag
@type matchTag: string
@keyword matchUse: match packages with given use flags
@type matchUse: list or tuple or set
@keyword packagesFilter: enable package masking filter
@type packagesFilter: bool
@keyword matchRevision: match packages with given entropy revision
@type matchRevision: int
@keyword extendedResults: return extended results
@type extendedResults: bool
@keyword useCache: use on-disk cache
@type useCache: bool
@return: tuple of length 2 composed by (idpackage 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
@todo: improve documentation here
"""
if not atom:
return -1, 1
if useCache:
cached = self.__atomMatchFetchCache(
atom, caseSensitive, matchSlot,
multiMatch, matchTag,
matchUse, packagesFilter, matchRevision,
extendedResults
)
if isinstance(cached, tuple):
try:
cached = self.__atomMatchValidateCache(cached,
multiMatch, extendedResults)
except (TypeError, ValueError, IndexError, KeyError,):
cached = None
if isinstance(cached, tuple):
return cached
atomTag = self.entropyTools.dep_gettag(atom)
try:
atomUse = self.entropyTools.dep_getusedeps(atom)
except InvalidAtom:
atomUse = ()
atomSlot = self.entropyTools.dep_getslot(atom)
atomRev = self.entropyTools.dep_get_entropy_revision(atom)
if isinstance(atomRev, (int, long,)):
if atomRev < 0: atomRev = None
# use match
scan_atom = self.entropyTools.remove_usedeps(atom)
if (not matchUse) and (atomUse):
matchUse = atomUse
# tag match
scan_atom = self.entropyTools.remove_tag(scan_atom)
if (matchTag is None) and (atomTag != None):
matchTag = atomTag
# slot match
scan_atom = self.entropyTools.remove_slot(scan_atom)
if (matchSlot is None) and (atomSlot != None):
matchSlot = atomSlot
# revision match
scan_atom = self.entropyTools.remove_entropy_revision(scan_atom)
if (matchRevision is None) and (atomRev != None):
matchRevision = atomRev
direction = ''
justname = True
pkgkey = ''
pkgname = ''
pkgcat = ''
pkgversion = ''
strippedAtom = ''
foundIDs = []
dbpkginfo = set()
if scan_atom:
while 1:
# check for direction
strippedAtom = self.entropyTools.dep_getcpv(scan_atom)
if scan_atom[-1] == "*":
strippedAtom += "*"
direction = scan_atom[0:-len(strippedAtom)]
justname = self.entropyTools.isjustname(strippedAtom)
pkgkey = strippedAtom
if justname == 0:
# get version
data = self.entropyTools.catpkgsplit(strippedAtom)
if data is None:
break # badly formatted
pkgversion = data[2]+"-"+data[3]
pkgkey = self.entropyTools.dep_getkey(strippedAtom)
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
foundIDs = self.__generate_found_ids_match(pkgkey, pkgname, pkgcat,
caseSensitive, multiMatch)
### FILTERING
# filter slot and tag
if foundIDs:
foundIDs = self.__filterSlotTagUse(foundIDs, matchSlot,
matchTag, matchUse, direction)
if packagesFilter:
foundIDs = self._packagesFilter(foundIDs)
### END FILTERING
if foundIDs:
dbpkginfo = self.__handle_found_ids_match(foundIDs, direction,
matchTag, matchRevision, justname, strippedAtom, pkgversion)
if not dbpkginfo:
if extendedResults:
if multiMatch:
x = set()
else:
x = (-1, 1, None, None, None,)
self.__atomMatchStoreCache(
atom, caseSensitive, matchSlot,
multiMatch, matchTag,
matchUse, packagesFilter, matchRevision,
extendedResults, result = (x, 1)
)
return x, 1
else:
if multiMatch:
x = set()
else:
x = -1
self.__atomMatchStoreCache(
atom, caseSensitive, matchSlot,
multiMatch, matchTag,
matchUse, packagesFilter, matchRevision,
extendedResults, result = (x, 1)
)
return x, 1
if multiMatch:
if extendedResults:
x = set([(x[0], 0, x[1], self.retrieveVersionTag(x[0]), \
self.retrieveRevision(x[0])) for x in dbpkginfo])
self.__atomMatchStoreCache(
atom, caseSensitive, matchSlot,
multiMatch, matchTag,
matchUse, packagesFilter, matchRevision,
extendedResults, result = (x, 0)
)
return x, 0
else:
x = set([x[0] for x in dbpkginfo])
self.__atomMatchStoreCache(
atom, caseSensitive, matchSlot,
multiMatch, matchTag,
matchUse, packagesFilter, matchRevision,
extendedResults, result = (x, 0)
)
return x, 0
if len(dbpkginfo) == 1:
x = dbpkginfo.pop()
if extendedResults:
x = (x[0], 0, x[1], self.retrieveVersionTag(x[0]),
self.retrieveRevision(x[0]),)
self.__atomMatchStoreCache(
atom, caseSensitive, matchSlot,
multiMatch, matchTag,
matchUse, packagesFilter, matchRevision,
extendedResults, result = (x, 0)
)
return x, 0
else:
self.__atomMatchStoreCache(
atom, caseSensitive, matchSlot,
multiMatch, matchTag,
matchUse, packagesFilter, matchRevision,
extendedResults, result = (x[0], 0)
)
return x[0], 0
dbpkginfo = list(dbpkginfo)
pkgdata = {}
versions = set()
for x in dbpkginfo:
info_tuple = (x[1], self.retrieveVersionTag(x[0]), \
self.retrieveRevision(x[0]))
versions.add(info_tuple)
pkgdata[info_tuple] = x[0]
newer = self.entropyTools.get_entropy_newer_version(list(versions))[0]
x = pkgdata[newer]
if extendedResults:
x = (x, 0, newer[0], newer[1], newer[2])
self.__atomMatchStoreCache(
atom, caseSensitive, matchSlot,
multiMatch, matchTag,
matchUse, packagesFilter, matchRevision,
extendedResults, result = (x, 0)
)
return x, 0
else:
self.__atomMatchStoreCache(
atom, caseSensitive, matchSlot,
multiMatch, matchTag,
matchUse, packagesFilter, matchRevision,
extendedResults, result = (x, 0)
)
return x, 0
def __generate_found_ids_match(self, pkgkey, pkgname, pkgcat, caseSensitive,
multiMatch):
if pkgcat == "null":
results = self.searchPackagesByName(pkgname,
sensitive = caseSensitive, justid = True)
else:
results = self.searchPackagesByNameAndCategory(name = pkgname,
category = pkgcat, sensitive = caseSensitive, justid = True
)
mypkgcat = pkgcat
mypkgname = pkgname
virtual = False
# if it's a PROVIDE, search with searchProvide
# there's no package with that name
if (not results) and (mypkgcat == "virtual"):
virtuals = self.searchProvide(pkgkey, justid = True)
if virtuals:
virtual = True
mypkgname = self.retrieveName(virtuals[0])
mypkgcat = self.retrieveCategory(virtuals[0])
results = virtuals
if not results: # nothing found
return set()
if len(results) > 1: # need to choose
# if it's because category differs, it's a problem
foundCat = None
cats = set()
for idpackage in results:
cat = self.retrieveCategory(idpackage)
cats.add(cat)
if (cat == mypkgcat) or ((not virtual) and \
(mypkgcat == "virtual") and (cat == mypkgcat)):
# in case of virtual packages only
# (that they're not stored as provide)
foundCat = cat
# if we found something at least...
if (not foundCat) and (len(cats) == 1) and \
(mypkgcat in ("virtual", "null")):
foundCat = sorted(cats)[0]
if not foundCat:
# got the issue
return set()
# we can use foundCat
mypkgcat = foundCat
# we need to search using the category
if (not multiMatch) and (pkgcat == "null" or virtual):
# we searched by name, we need to search using category
results = self.searchPackagesByNameAndCategory(
name = mypkgname, category = mypkgcat,
sensitive = caseSensitive, justid = True
)
# if we get here, we have found the needed IDs
return set(results)
###
### just found one result
###
idpackage = results[0]
# if mypkgcat is virtual, it can be forced
if (mypkgcat == "virtual") and (not virtual):
# in case of virtual packages only
# (that they're not stored as provide)
mypkgcat = self.retrieveCategory(idpackage)
# check if category matches
if mypkgcat != "null":
foundCat = self.retrieveCategory(idpackage)
if mypkgcat == foundCat:
return set([idpackage])
return set() # nope nope
# very good, here it is
return set([idpackage])
def __handle_found_ids_match(self, foundIDs, direction, matchTag,
matchRevision, justname, strippedAtom, pkgversion):
dbpkginfo = set()
# now we have to handle direction
if ((direction) or ((not direction) and (not justname)) or \
((not direction) and (not justname) \
and strippedAtom.endswith("*"))) and foundIDs:
if (not justname) and \
((direction == "~") or (direction == "=") or \
(direction == '' and not justname) or (direction == '' and \
not justname and strippedAtom.endswith("*"))):
# any revision within the version specified
# OR the specified version
if (direction == '' and not justname):
direction = "="
# remove gentoo revision (-r0 if none)
if (direction == "="):
if (pkgversion.split("-")[-1] == "r0"):
pkgversion = self.entropyTools.remove_revision(
pkgversion)
if (direction == "~"):
pkgrevision = self.entropyTools.dep_get_portage_revision(
pkgversion)
pkgversion = self.entropyTools.remove_revision(pkgversion)
for idpackage in foundIDs:
dbver = self.retrieveVersion(idpackage)
if (direction == "~"):
myrev = self.entropyTools.dep_get_portage_revision(
dbver)
myver = self.entropyTools.remove_revision(dbver)
if myver == pkgversion and pkgrevision <= myrev:
# found
dbpkginfo.add((idpackage, dbver))
else:
# media-libs/test-1.2* support
if pkgversion[-1] == "*":
if dbver.startswith(pkgversion[:-1]):
dbpkginfo.add((idpackage, dbver))
elif (matchRevision != None) and (pkgversion == dbver):
dbrev = self.retrieveRevision(idpackage)
if dbrev == matchRevision:
dbpkginfo.add((idpackage, dbver))
elif (pkgversion == dbver) and (matchRevision is None):
dbpkginfo.add((idpackage, dbver))
elif (direction.find(">") != -1) or (direction.find("<") != -1):
if not justname:
# remove revision (-r0 if none)
if pkgversion.endswith("r0"):
# remove
self.entropyTools.remove_revision(pkgversion)
for idpackage in foundIDs:
revcmp = 0
tagcmp = 0
if matchRevision != None:
dbrev = self.retrieveRevision(idpackage)
revcmp = cmp(matchRevision, dbrev)
if matchTag != None:
dbtag = self.retrieveVersionTag(idpackage)
tagcmp = cmp(matchTag, dbtag)
dbver = self.retrieveVersion(idpackage)
pkgcmp = self.entropyTools.compare_versions(
pkgversion, dbver)
if pkgcmp is None:
import warnings
warnings.warn("WARNING, invalid version string " + \
"stored in %s: %s <-> %s" % (
self.dbname, pkgversion, dbver,)
)
continue
if direction == ">":
if pkgcmp < 0:
dbpkginfo.add((idpackage, dbver))
elif (matchRevision != None) and pkgcmp <= 0 \
and revcmp < 0:
dbpkginfo.add((idpackage, dbver))
elif (matchTag != None) and tagcmp < 0:
dbpkginfo.add((idpackage, dbver))
elif direction == "<":
if pkgcmp > 0:
dbpkginfo.add((idpackage, dbver))
elif (matchRevision != None) and pkgcmp >= 0 \
and revcmp > 0:
dbpkginfo.add((idpackage, dbver))
elif (matchTag != None) and tagcmp > 0:
dbpkginfo.add((idpackage, dbver))
elif direction == ">=":
if (matchRevision != None) and pkgcmp <= 0:
if pkgcmp == 0:
if revcmp <= 0:
dbpkginfo.add((idpackage, dbver))
else:
dbpkginfo.add((idpackage, dbver))
elif pkgcmp <= 0 and matchRevision is None:
dbpkginfo.add((idpackage, dbver))
elif (matchTag != None) and tagcmp <= 0:
dbpkginfo.add((idpackage, dbver))
elif direction == "<=":
if (matchRevision != None) and pkgcmp >= 0:
if pkgcmp == 0:
if revcmp >= 0:
dbpkginfo.add((idpackage, dbver))
else:
dbpkginfo.add((idpackage, dbver))
elif pkgcmp >= 0 and matchRevision is None:
dbpkginfo.add((idpackage, dbver))
elif (matchTag != None) and tagcmp >= 0:
dbpkginfo.add((idpackage, dbver))
else: # just the key
dbpkginfo = set([(x, self.retrieveVersion(x),) for x in foundIDs])
return dbpkginfo