#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
"""Interfaces and implementation for the Catalog object, as well as functions
that operate on lists of package FMRIs."""
import copy
import calendar
import collections
import datetime
import errno
import fnmatch
import hashlib
import os
import six
import stat
import threading
import types
"""Private helper class used to serialize catalog data and generate
signatures."""
# Determines whether data is encoded in a single pass (uses
# more memory) or iteratively.
# Default to a 32K buffer.
# catalog signatures *must* use sha-1 only since clients
# compare entire dictionaries against the reported hash from
# the catalog in the various <CatalogPartBase>.validate()
# methods rather than just attributes within those dictionaries.
# If old clients are to interoperate with new repositories, the
# computed and expected dictionaries must be identical at
# present, so we must use sha-1.
if sign:
if not pathname:
# Only needed if not writing to __fileobj.
self.__sha_1_value = None
if not pathname:
return
# Call statvfs to find optimal blocksize for destination.
try:
# Set the file buffer size to the blocksize of our
# filesystem.
except EnvironmentError as e:
raise api_errors.PermissionsException(
e.filename)
except AttributeError as e:
# os.statvfs is not available on some platforms.
pass
try:
except EnvironmentError as e:
raise api_errors.PermissionsException(
e.filename)
e.filename)
raise
"""Returns a dictionary mapping digest algorithms to the
hex-encoded digest values of the text of the catalog."""
return {}
"""Serializes and stores the provided data in JSON format."""
# sort_keys is necessary to ensure consistent signature
# generation. It has a minimal performance cost as well (on
# on SPARC and x86), so shouldn't be an issue. However, it
# is only needed if the caller has indicated that the content
# should be signed.
# Whenever possible, avoid using the write wrapper (self) as
# this can greatly increase write times.
if not out:
# Can't sign unless a file object is provided. And if
# one is provided, but no signing is to be done, then
# ensure the fileobject is discarded.
return
# Ensure file object goes out of scope.
# Calculating sha-1 this way is much faster than intercepting
# write calls because of the excessive number of write calls
# that json.dump() triggers (1M+ for /dev catalog files).
# Open the JSON file so that the signature data can be added.
# The last bytes should be "}\n", which is where the
# signature data structure needs to be appended.
# Add the signature data and close.
if sfoffset > 1:
# Catalog is not empty, so a separator is needed.
"""Wrapper function that should not be called by external
consumers."""
"""Wrapper function that should not be called by external
consumers."""
for l in iterable:
"""A CatalogPartBase object is an abstract class containing core
functionality shared between CatalogPart and CatalogAttrs."""
# The file mode to be used for all catalog files.
__meta_root = None
__file_root = None
last_modified = None
name = None
signatures = None
"""Initializes a CatalogPartBase object."""
# Sanity check: part names can't be pathname-ish.
self.signatures = {}
# Operations shouldn't attempt to load the part data
# unless meta_root is defined and the data exists.
else:
f = _JSONWriter(data)
f.save()
return f.signatures()
return self.__meta_root
return self.__file_root
"""A UTC datetime object representing the time the file used to
to store object metadata was modified, or None if it does not
exist yet."""
return None
try:
except EnvironmentError as e:
return None
raise
if path:
if path:
"""Removes any on-disk files that exist for the catalog part and
discards all content."""
try:
except EnvironmentError as e:
raise api_errors.PermissionsException(
e.filename)
e.filename)
raise
self.signatures = {}
self.last_modified = None
"""A boolean value indicating wheher a file for the catalog part
exists at <self.meta_root>/<self.name>."""
return False
"""Load the serialized data for the catalog part and return the
resulting structure."""
try:
except EnvironmentError as e:
raise api_errors.RetrievalError(e,
e.filename)
raise api_errors.PermissionsException(
e.filename)
raise
try:
except EnvironmentError as e:
raise api_errors.RetrievalError(e)
except ValueError as e:
# Not a valid catalog file.
# Signature data, if present, should be removed from the struct
# on load and then stored in the signatures object property.
return struct
"""The absolute path of the file used to store the data for
this part or None if meta_root or name is not set."""
return None
"""Serialize and store the transformed catalog part's 'data' in
a file using the pathname <self.meta_root>/<self.name>.
'data' must be a dict.
'single_pass' is an optional boolean indicating whether the data
should be serialized in a single pass. This is significantly
faster, but requires that the entire set of data be serialized
in-memory instead of iteratively writing it to the target
storage object."""
f.save()
# Update in-memory copy to reflect stored data.
# Ensure the permissions on the new file are correct.
try:
except EnvironmentError as e:
raise api_errors.PermissionsException(
e.filename)
e.filename)
raise
# Finally, set the file times to match the last catalog change.
if self.last_modified:
"""A CatalogPart object is the representation of a subset of the package
FMRIs available from a package repository."""
__data = None
ordered = None
file_root=None):
"""Initializes a CatalogPart object."""
"""Private generator function to iterate over catalog entries.
'last' is a boolean value that indicates only the last entry
for each package on a per-publisher basis should be returned.
As long as the CatalogPart has been saved since the last
modifying operation, or sort() has has been called, this will
also be the newest version of the package.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
if ordered:
else:
stems = (
)
if last:
return (
)
if ordered:
return (
)
return (
)
"""Add a catalog entry for a given FMRI or FMRI components.
'metadata' is an optional dict containing the catalog
metadata that should be stored for the specified FMRI.
The dict representing the entry is returned to callers,
but should not be modified.
"""
# Hot path, so avoid calling load unless necessary, even
# though it performs this check already.
if pfmri:
if not pfmri:
raise api_errors.DuplicateCatalogEntry(
if metadata is not None:
else:
entry = {}
if not op_time:
self.signatures = {}
return entry
"""Removes any on-disk files that exist for the catalog part and
discards all content."""
"""A generator function that produces tuples of the form
(fmri, entry) as it iterates over the contents of the catalog
part (where entry is the related catalog entry for the fmri).
Callers should not modify any of the data that is returned.
'cb' is an optional callback function that will be executed for
each package. It must accept two arguments: 'pkg' and 'entry'.
'pkg' is an FMRI object and 'entry' is the dictionary structure
of the catalog entry for the package. If the callback returns
False, then the entry will not be included in the results.
'last' is a boolean value that indicates only the last entry
for each package on a per-publisher basis should be returned.
As long as the CatalogPart has been saved since the last
modifying operation, or sort() has has been called, this will
also be the newest version of the package.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pubs' is an optional list of publisher prefixes to restrict
the results to.
Results are always in catalog version order on a per-
publisher, per-stem basis.
"""
yield f, entry
"""A generator function that produces tuples of (version,
entries), where entries is a list of tuples of the format
(fmri, entry) where entry is the catalog entry for the
FMRI) as it iterates over the CatalogPart contents.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
versions = {}
entries = {}
"""A generator function that produces FMRIs as it iterates
over the contents of the catalog part.
'last' is a boolean value that indicates only the last fmri
for each package on a per-publisher basis should be returned.
As long as the CatalogPart has been saved since the last
modifying operation, or sort() has has been called, this will
also be the newest version of the package.
'objects' is an optional boolean value indicating whether
FMRIs should be returned as FMRI objects or as strings.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pubs' is an optional list of publisher prefixes to restrict
the results to.
Results are always in catalog version order on a per-
publisher, per-stem basis."""
if objects:
return
return
"""A generator function that produces tuples of (version,
fmris), where fmris is a list of the fmris related to the
version.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
versions = {}
entries = {}
if not ver_list:
continue
"""Returns the catalog part entry for the given package FMRI or
FMRI components."""
# Since this is a hot path, this function checks for loaded
# status before attempting to call the load function.
if pfmri:
if not pkg_list:
return
return entry
"""Returns a tuple of integer values (package_count,
package_version_count). The first is the number of
unique packages (per-publisher), and the second is the
number of unique package versions (per-publisher and
stem)."""
package_count = 0
package_count += 1
return (package_count, package_version_count)
"""Returns a generator of tuples of the form (pub,
package_count, package_version_count). 'pub' is the publisher
prefix, 'package_count' is the number of unique packages for the
publisher, and 'package_version_count' is the number of unique
package versions for the publisher.
"""
package_count = 0
package_count += 1
"""Load and transform the catalog part's data, preparing it
for use."""
# Already loaded, or only in-memory.
return
"""Returns a set containing the names of all the packages in
the CatalogPart.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
return set((
))
"""A generator function that produces package tuples of the form
(pub, stem) as it iterates over the contents of the CatalogPart.
'pubs' is an optional list of publisher prefixes to restrict
the results to. If specified, publishers will be sorted in
the order given.
Results are always returned sorted by stem and then by
publisher."""
# Results have to be sorted by stem first, and by
# publisher prefix second.
pkg_list = [
]
pub_sort = None
if pubs:
def pub_key(a):
"""A generator function that returns publisher prefixes as it
iterates over the package data in the CatalogPart.
'pubs' is an optional list that contains the prefixes of the
publishers to restrict the results to."""
# Any entries starting with "_" are part of the
# reserved catalog namespace.
yield pub
"""Remove a package and its metadata."""
if not pkg_list:
# Safe to do this since a 'break' is done
# immediately after removals are performed.
del ver_list[i]
if not ver_list:
# When all version entries for a
# package are removed, its stem
# should be also.
if not pkg_list:
# When all package stems for a
# publisher have been removed,
# it should be also.
break
else:
if not op_time:
self.signatures = {}
"""Transform and store the catalog part's data in a file using
the pathname <self.meta_root>/<self.name>.
'single_pass' is an optional boolean indicating whether the data
should be serialized in a single pass. This is significantly
faster, but requires that the entire set of data be serialized
in-memory instead of iteratively writing it to the target
storage object."""
# Assume this is in-memory only.
return
# Ensure content is loaded before attempting save.
"""Re-sorts the contents of the CatalogPart such that version
entries for each package stem are in ascending order.
'pfmris' is an optional set of FMRIs to restrict the sort to.
This is useful during catalog operations as only entries for
the corresponding package stem(s) need to be sorted.
'pubs' is an optional set of publisher prefixes to restrict
the sort to. This is useful during catalog operations as only
entries for the corresponding publisher stem(s) need to be
sorted. This option has no effect if 'pfmris' is also
provided.
If neither 'pfmris' or 'pubs' is provided, all entries will be
sorted."""
if pfmris is not None:
for f in pfmris:
pkg_stem = f.get_pkg_stem()
continue
# The specified FMRI may not exist in this
# CatalogPart, so continue if it does not
# exist.
if pkg_list:
None)
if ver_list:
return
"""A generator function that produces FMRI tuples as it
iterates over the contents of the catalog part.
'last' is a boolean value that indicates only the last FMRI
tuple for each package on a per-publisher basis should be
returned. As long as the CatalogPart has been saved since
the last modifying operation, or sort() has has been called,
this will also be the newest version of the package.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
return (
)
"""A generator function that produces tuples of the form ((pub,
stem, version), entry) as it iterates over the contents of the
catalog part (where entry is the related catalog entry for the
fmri). Callers should not modify any of the data that is
returned.
'cb' is an optional callback function that will be executed for
each package. It must accept two arguments: 'pkg' and 'entry'.
'pkg' is an FMRI tuple and 'entry' is the dictionary structure
of the catalog entry for the package. If the callback returns
False, then the entry will not be included in the results.
'last' is a boolean value that indicates only the last entry
for each package on a per-publisher basis should be returned.
As long as the CatalogPart has been saved since the last
modifying operation, or sort() has has been called, this will
also be the newest version of the package.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pubs' is an optional list of publisher prefixes to restrict
the results to.
Results are always in catalog version order on a per-publisher,
per-stem basis."""
yield t, entry
"""Verifies whether the signatures for the contents of the
CatalogPart match the specified signature data, or if not
provided, the current signature data. Raises the exception
named 'BadCatalogSignatures' on failure."""
not require_signatures:
# Nothing to validate, and we're not required to.
return
# Ensure content is loaded before attempting to retrieve
# or generate signature data.
if not signatures:
if new_signatures != signatures:
"""A CatalogUpdate object is an augmented representation of a subset
of the package data contained within a Catalog."""
# Properties.
__data = None
last_modified = None
# Operation constants.
"""Initializes a CatalogUpdate object."""
"""Records the specified catalog operation and any related
catalog metadata for the specified package FMRI.
'operation' must be one of the following constant values
provided by the CatalogUpdate class:
ADD
REMOVE
'op_time' is a UTC datetime object indicating the time
the catalog operation was performed.
'metadata' is an optional dict containing the catalog
metadata that should be stored for the specified FMRI
indexed by catalog part (e.g. "dependency", "summary",
etc.)."""
if metadata is not None:
else:
entry = {}
# To ensure the update log is viewed as having been updated
# at the exact same time as the catalog, the last_modified
# time of the update log must match the operation time.
self.signatures = {}
"""Load and transform the catalog update's data, preparing it
for use."""
# Already loaded, or only in-memory.
return
"""A generator function that returns publisher prefixes as it
iterates over the package data in the CatalogUpdate."""
# Any entries starting with "_" are part of the
# reserved catalog namespace.
yield pub
"""Transform and store the catalog update's data in a file using
the pathname <self.meta_root>/<self.name>."""
# Assume this is in-memory only.
return
# Ensure content is loaded before attempting save.
"""A generator function that produces tuples of the format
(fmri, op_type, op_time, metadata). Where:
* 'fmri' is a PkgFmri object for the package.
* 'op_type' is a CatalogUpdate constant indicating
the catalog operation performed.
* 'op_time' is a UTC datetime object representing the
time time the catalog operation was performed.
* 'metadata' is a dict containing the catalog metadata
for the FMRI indexed by catalog part name.
Results are always in ascending operation time order on a
per-publisher, per-stem basis.
"""
mdata = {}
return
"""Verifies whether the signatures for the contents of the
CatalogUpdate match the specified signature data, or if not
provided, the current signature data. Raises the exception
named 'BadCatalogSignatures' on failure."""
not require_signatures:
# Nothing to validate, and we're not required to.
return
# Ensure content is loaded before attempting to retrieve
# or generate signature data.
if not signatures:
if new_signatures != signatures:
"""A CatalogAttrs object is the representation of the attributes of a
Catalog object."""
# Properties.
__data = None
# This structure defines defaults (for use in __init__) as well as
# the set of required elements for this catalog part. See also the
# logic in load().
__DEFAULT_ELEMS = {
"created": None,
"last-modified": None,
"package-count": 0,
"package-version-count": 0,
"parts": {},
"updates": {},
"version": 1,
}
"""Initializes a CatalogAttrs object."""
# If the data is already seen as 'loaded' during init,
# this is actually a new object, so setup some sane
# defaults.
else:
# Assume that the attributes of the catalog can be
# obtained from a file.
self.signatures = {}
self.signatures = {}
self.signatures = {}
self.signatures = {}
self.signatures = {}
self.signatures = {}
self.signatures = {}
"""Duplicate and transform 'self.__data' for saving."""
# Use a copy to prevent the in-memory version from being
# affected by the transformations.
# Convert datetime objects to an ISO-8601
# basic format string.
continue
for e in val:
if lm:
return struct
"""Load and transform the catalog attribute data."""
# Already loaded, or only in-memory.
return
# Check to see that struct is as we expect: it must be a dict
# and have all of the elements in self.__DEFAULT_ELEMS.
try:
return basic_ts_to_datetime(val)
except ValueError:
# Convert ISO-8601 basic format strings to
# datetime objects.
if val:
continue
raise api_errors.InvalidCatalogFile(
# 'parts' and 'updates' have a more complex
# structure. Check that all of the subparts
# look sane.
raise api_errors.\
"{0} {{{1}: {2}}}".format(
# Check if subpart is a symbolic link
# that would cause an access to be
# redirected outside of 'file_root'.
try:
except OSError as e:
raise api_errors.\
"{0} {{{1}: {2}}}".format(
# Build datetimes from timestamps.
for e in val:
if lm:
"""Transform and store the catalog attribute data in a file
using the pathname <self.meta_root>/<self.name>."""
# Assume this is in-memory only.
return
# Ensure content is loaded before attempting save.
"""Verifies whether the signatures for the contents of the
CatalogAttrs match the specified signature data, or if not
provided, the current signature data. Raises the exception
named 'BadCatalogSignatures' on failure."""
not require_signatures:
# Nothing to validate, and we're not required to.
return
# Ensure content is loaded before attempting to retrieve
# or generate signature data.
if not signatures:
if new_signatures != signatures:
"""A Catalog is the representation of the package FMRIs available from
a package repository."""
# The file mode to be used for all catalog files.
# These properties are declared here so that they show up in the pydoc
# documentation as private, and for clarity in the property declarations
# found near the end of the class definition.
_attrs = None
__batch_mode = None
__lock = None
__meta_root = None
__file_root = None
__sign = None
# These are used to cache or store CatalogPart and CatalogUpdate objects
# as they are used. It should not be confused with the CatalogPart
# names and CatalogUpdate names stored in the CatalogAttrs object.
__parts = None
__updates = None
# Class Constants
"""Initializes a Catalog object.
'batch_mode' is an optional boolean value that indicates that
the caller intends to perform multiple modifying operations on
catalog before saving. This is useful for performance reasons
as the contents of the catalog will not be sorted after each
change, and the package counts will not be updated (except at
save()). By default this value is False. If this value is
True, callers are responsible for calling finalize() to ensure
that catalog entries are in the correct order and package counts
accurately reflect the catalog contents.
'meta_root' is an optional absolute pathname of a directory
that catalog metadata can be written to and read from, and
must already exist. If no path is supplied, then it is
assumed that the catalog object will be used for in-memory
operations only.
'log_updates' is an optional boolean value indicating whether
updates to the catalog should be logged. This enables consumers
of the catalog to perform incremental updates.
'read_only' is an optional boolean value that indicates if
operations that modify the catalog are allowed (an assertion
error will be raised if one is attempted and this is True).
'sign' is an optional boolean value that indicates that the
the catalog data should have signature data generated and
embedded when serialized. This option is primarily a matter
of convenience for callers that wish to trade integrity checks
for improved catalog serialization performance."""
# Must be set after the above.
# Must be set after the above.
# This lock is used to protect the catalog file from multiple
# threads writing to it at the same time.
# Must be done last.
assert info_needed
if not locales:
else:
try:
except KeyError:
yield f, EmptyI
"""Private version; caller responsible for locking."""
if src_base is None:
if pfmri:
# Nothing to do
return
# Use the same operation time and date for all operations so
# that the last modification times will be synchronized. This
# also has the benefit of avoiding extra datetime object
# instantiations.
# For each entry in the 'src' catalog, add its BASE entry to the
# current catalog along and then add it to the 'd'iscard dict if
# 'cb' is defined and returns False.
if pfmri:
if entry is None:
raise api_errors.UnknownCatalogEntry(
else:
d = {}
continue
if cb is not None:
if not merge:
set())
continue
if mdata:
if "metadata" in nentry:
else:
if d and pfmri:
# If the 'd'iscards dict is populated and pfmri is
# defined, then there is nothing more to do.
return
# Finally, merge any catalog part entries that exist unless the
# FMRI is found in the 'd'iscard dict.
continue
if part is None:
# Part doesn't exist in-memory or on-disk, so
# skip it.
continue
if pfmri:
if entry is None:
# Package isn't in this part; skip it.
continue
else:
continue
if f.publisher in d and \
# Skip this package.
continue
if base is None:
# Catalog contains nothing.
return
if not locales:
else:
parts = []
if part is not None:
if part is None:
# Data not available for this
# locale.
continue
if k == "actions":
dest.setdefault(k, [])
dest[k] += v
elif k != "version":
dest[k] = v
if tuples:
mdata = {}
if entry is None:
# Part doesn't have this FMRI,
# so skip it.
continue
if k == "actions":
mdata.setdefault(k, [])
mdata[k] += v
elif k != "version":
mdata[k] = v
yield r, mdata
return
mdata = {}
if entry is None:
# Part doesn't have this FMRI,
# so skip it.
continue
if k == "actions":
mdata.setdefault(k, [])
mdata[k] += v
elif k != "version":
mdata[k] = v
yield f, mdata
"""Private finalize method; exposes additional controls for
internal callers."""
package_count = 0
if part is not None:
# If the base Catalog didn't exist (in-memory or on-
# disk) that implies there is nothing to sort and
# there are no packages (since the base catalog part
# must always exist for packages to be present).
if sort:
# Some operations don't need this, such as
# remove...
errors = None
# pfmri is assumed to be a FMRI tuple.
else:
try:
# Accumulate errors and continue so that as
# much of the action data as possible can be
# parsed.
if errors is None:
# Allocate this here to avoid overhead
# of list allocation/deallocation.
errors = []
continue
if a.name == "set" and \
# Don't filter actual facet or variant
# set actions.
yield a
elif a.include_this(excludes,
yield a
if errors is not None:
"""Private helper function to iterate over a Manifest's actions
by action type, returning tuples of (action, attr_name)."""
for a in m.gen_actions_by_type(atype):
if not a.include_this(excludes,
continue
if atype == "set":
yield a, a.attrs["name"]
else:
yield a, None
return self.__batch_mode
return self.__meta_root
return self.__file_root
# First, check if the update has already been cached,
# and if so, return it.
if ulog is not None:
return ulog
return
# Next, if the update hasn't been cached,
# create an object for it.
# Update doesn't exist on-disk,
# so don't return anything.
return
if cache:
return ulog
"""Locks the catalog preventing multiple threads or external
consumers of the catalog from modifying it during operations.
"""
# XXX need filesystem lock too?
"""Helper function to log catalog changes."""
if not self.__batch_mode:
# The catalog.attrs needs to be updated to reflect
# the changes made. A sort doesn't need to be done
# here as the individual parts will automatically do
# that as needed in this case.
# This must be set to exactly the same time as the update logs
# so that the changes in the update logs are not marked as
# being newer than the catalog or vice versa.
if not self.log_updates:
return
updates = {}
# The last component of the updatelog filename is the
# related locale.
"last-modified": op_time
}
# Signature data for each part needs to be cleared,
# and will only be available again after save().
}
"""A generator function that yields a list of tuples of the form
(pattern, error, fmri, matcher) based on the provided patterns,
where 'error' is any exception encountered while parsing the
pattern, 'fmri' is the resulting FMRI object, and 'matcher' is
one of the following pkg.fmri matching functions:
pkg.fmri.exact_name_match
Indicates that the name portion of the pattern
must match exactly and the version (if provided)
must be considered a successor or equal to the
target FMRI.
pkg.fmri.fmri_match
Indicates that the name portion of the pattern
must be a proper subset and the version (if
provided) must be considered a successor or
equal to the target FMRI.
pkg.fmri.glob_match
Indicates that the name portion of the pattern
uses fnmatch rules for pattern matching (shell-
style wildcards) and that the version can either
match exactly, match partially, or contain
wildcards.
"""
error = None
matcher = None
npat = None
try:
pat_ver = None
else:
else:
if not pat_ver:
# Do nothing.
pass
pat_ver == "latest":
else:
# Whatever the error was, return it.
error = e
"""Private save function. Caller is responsible for locking
the catalog."""
if self.log_updates:
# Replace the existing signature data
# with the new signature data.
}
# Save any CatalogParts that are currently in-memory,
# updating their related information in catalog.attrs
# as they are saved.
# Must save first so that signature data is
# current.
# single-pass encoding is not used for summary part as
# it increases memory usage substantially (30MB at
# current for /dev). No significant difference is
# detectable for other parts though.
# Now replace the existing signature data with
# the new signature data.
}
# Finally, save the catalog attributes.
if pathname:
# If the Catalog's meta_root changes, the meta_root of all of
# its parts must be changed too.
if pathname:
# If the Catalog's file_root changes, the file_root of all of
# its parts must be changed too.
"""Sets permissions on attrs and parts if not read_only and if
the current user can do so; raises BadCatalogPermissions if the
permissions are wrong and cannot be corrected."""
# Nothing to do.
return
# Force file_mode, so that unprivileged users can read these.
bad_modes = []
try:
"{0:o}".format(
else:
except EnvironmentError as e:
# If the file doesn't exist yet, move on.
continue
# If the mode change failed for another reason,
# check to see if we actually needed to change
# it, and if so, add it to bad_modes.
if bad_modes:
# If the Catalog's sign property changes, the value of that
# property for its attributes, etc. must be changed too.
"""Unlocks the catalog allowing other catalog consumers to
modify it."""
# XXX need filesystem unlock too?
"""A generator function that produces tuples of the format
(fmri, actions) as it iterates over the contents of the
catalog (where 'actions' is a generator that returns the
Actions corresponding to the requested information).
If the catalog doesn't contain any action data for the package
entry, it will return an empty iterator.
'excludes' is a list of variants which will be used to determine
what should be allowed by the actions generator in addition to
what is specified by 'info_needed'.
'cb' is an optional callback function that will be executed for
each package before its action data is retrieved. It must accept
two arguments: 'pkg' and 'entry'. 'pkg' is an FMRI object and
'entry' is the dictionary structure of the catalog entry for the
package. If the callback returns False, then the entry will not
be included in the results. This can significantly improve
performance by avoiding action data retrieval for results that
will not be used.
'info_needed' is a set of one or more catalog constants
indicating the types of catalog data that will be returned
in 'actions' in addition to the above:
DEPENDENCY
Depend and set Actions for package obsoletion,
renaming, variants.
SUMMARY
Any remaining set Actions not listed above, such
as pkg.summary, pkg.description, etc.
'last' is a boolean value that indicates only the last entry
for each package on a per-publisher basis should be returned.
As long as the catalog has been saved since the last modifying
operation, or finalize() has has been called, this will also be
the newest version of the package.
'locales' is an optional set of locale names for which Actions
should be returned. The default is set(('C',)) if not provided.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pfmri' is an optional FMRI to limit the returned results to.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
"""Add a package and its related metadata to the catalog and
its parts as needed.
'manifest' is an optional Manifest object that will be used
to retrieve the metadata related to the package.
'metadata' is an optional dict of additional metadata to store
with the package's BASE record."""
dep_acts = { "C": [] }
# Summary actions are grouped by locale, since each
# goes to a locale-specific catalog part.
sum_acts = { "C": [] }
continue
# variant and facet data goes to the
# dependency catalog part.
continue
# Redundant in the case of the catalog.
continue
# All other set actions go to the summary
# catalog parts, grouped by locale. To
# determine the locale, the set attribute's
# name is split by ':' into its field and
# locale components. If ':' is not present,
# then the 'C' locale is assumed.
else:
locale = "C"
return {
"dependency": dep_acts,
"summary": sum_acts,
}
try:
entries = {}
# Use the same operation time and date for all
# operations so that the last modification times
# of all catalog parts and update logs will be
# synchronized.
# Always add packages to the base catalog.
entry = {}
if metadata:
if manifest:
if manifest:
# Without a manifest, only the base catalog data
# can be populated.
# Only dependency and set actions are currently
# used by the remaining catalog parts.
actions = []
if not acts:
# Catalog entries only
# added if actions are
# present for this
# ctype.
continue
locale))
finally:
"""Appends the entries in the specified 'src' catalog to that
of the current catalog. The caller is responsible for ensuring
that no duplicates exist and must call finalize() afterwards to
to ensure consistent catalog state. This function cannot be
used when log_updates or read_only is enabled.
'cb' is an optional callback function that must accept src,
an FMRI, and entry. Where 'src' is the source catalog the
FMRI's entry is being copied from, and entry is the source
catalog entry. It must return a tuple of the form (append,
metadata), where 'append' is a boolean value indicating if
the specified package should be appended, and 'metadata' is
a dict of additional metadata to store with the package's
BASE record.
'pfmri' is an optional FMRI of a package to append. If not
provided, all FMRIs in the 'src' catalog will be appended.
This filtering is applied before any provided callback.
'pubs' is an optional list of publisher prefixes to restrict
the append operation to. FRMIs that have a publisher not in
the list will be skipped. This filtering is applied before
any provided callback. If not provided, no publisher
filtering will be applied."""
try:
# Append operations are much slower if batch mode is
# not enabled. This ensures that the current state
# is stored and then reset on completion or failure.
# Since append() is never used as part of the
# publication process (log_updates == True),
# this is safe.
finally:
"""Apply any CatalogUpdates available to the catalog based on
the list returned by get_updates_needed. The caller must
retrieve all of the resources indicated by get_updates_needed
and place them in the directory indicated by 'path'."""
raise api_errors.CatalogUpdateRequirements()
# Used to store the original time each part was modified
# as a basis for determining whether to apply specific
# updates.
# Load the CatalogUpdate from the path specified.
# (Which is why __get_update is not used.)
if part is None:
# Part doesn't exist; skip.
continue
# Only add updates to the part
# that occurred after the last
# time it was originally
# modified.
continue
else:
raise api_errors.UnknownUpdateType(
try:
if updates == None:
# Nothing has changed, so nothing to do.
return
# The provided update is an incremental.
else:
# The provided update is a full update.
# Next, verify that all of the updated parts have a
# signature that matches the new catalog.attrs file.
new_sigs = {}
continue
# This must be done to ensure that the catalog
# signature matches that of the source.
# Finally, save the catalog, and then copy the new
# catalog attributes file into place and reload it.
finally:
"""Returns a set of tuples of the form (scheme, category)
containing the names of all categories in use by the last
version of each unique package in the catalog on a per-
publisher basis.
'excludes' is a list of variants which will be used to
determine what category actions will be checked.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
return set((
for a in acts
if a.has_category_info()
for sc in a.parse_category_info()
))
"""A UTC datetime object indicating the time the catalog was
created."""
"""Removes any on-disk files that exist for the catalog and
discards all content."""
return
# Finally, ensure that if there are any leftover files from
# an interrupted destroy in the past that they are removed
# as well.
continue
continue
try:
except EnvironmentError as e:
raise api_errors.PermissionsException(
e.filename)
e.filename)
raise
"""A generator function that produces tuples of the format
(fmri, metadata) as it iterates over the contents of the
catalog (where 'metadata' is a dict containing the requested
information).
'metadata' always contains the following information at a
minimum:
BASE
'metadata' will be populated with Manifest
signature data, if available, using key-value
pairs of the form 'signature-<name>': value.
'info_needed' is an optional list of one or more catalog
constants indicating the types of catalog data that will
be returned in 'metadata' in addition to the above:
DEPENDENCY
'metadata' will contain depend and set Actions
for package obsoletion, renaming, variants,
and facets stored in a list under the
key 'actions'.
SUMMARY
'metadata' will contain any remaining Actions
not listed above, such as pkg.summary,
pkg.description, etc. in a list under the key
'actions'.
'last' is a boolean value that indicates only the last entry
for each package on a per-publisher basis should be returned.
As long as the catalog has been saved since the last modifying
operation, or finalize() has has been called, this will also be
the newest version of the package.
'locales' is an optional set of locale names for which Actions
should be returned. The default is set(('C',)) if not provided.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
"""A generator function that produces tuples of the format
(version, entries) as it iterates over the contents of the
the catalog, where entries is a list of tuples of the format
(fmri, metadata) and metadata is a dict containing the
requested information.
'metadata' always contains the following information at a
minimum:
BASE
'metadata' will be populated with Manifest
signature data, if available, using key-value
pairs of the form 'signature-<name>': value.
'info_needed' is an optional list of one or more catalog
constants indicating the types of catalog data that will
be returned in 'metadata' in addition to the above:
DEPENDENCY
'metadata' will contain depend and set Actions
for package obsoletion, renaming, variants,
and facets stored in a list under the
key 'actions'.
SUMMARY
'metadata' will contain any remaining Actions
not listed above, such as pkg.summary,
pkg.description, etc. in a list under the key
'actions'.
'locales' is an optional set of locale names for which Actions
should be returned. The default is set(('C',)) if not provided.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
if base is None:
# Catalog contains nothing.
return
if not locales:
else:
parts = []
if part is not None:
if part is None:
# Data not available for this
# locale.
continue
if k == "actions":
dest.setdefault(k, [])
dest[k] += v
elif k != "version":
dest[k] = v
nentries = []
mdata = {}
if entry is None:
# Part doesn't have this FMRI,
# so skip it.
continue
"""A generator function that produces tuples of the format
((pub, stem, version), entry, actions) as it iterates over
the contents of the catalog (where 'actions' is a generator
that returns the Actions corresponding to the requested
information).
If the catalog doesn't contain any action data for the package
entry, it will return an empty iterator.
'excludes' is a list of variants which will be used to determine
what should be allowed by the actions generator in addition to
what is specified by 'info_needed'.
'cb' is an optional callback function that will be executed for
each package before its action data is retrieved. It must accept
two arguments: 'pkg' and 'entry'. 'pkg' is an FMRI object and
'entry' is the dictionary structure of the catalog entry for the
package. If the callback returns False, then the entry will not
be included in the results. This can significantly improve
performance by avoiding action data retrieval for results that
will not be used.
'info_needed' is a set of one or more catalog constants
indicating the types of catalog data that will be returned
in 'actions' in addition to the above:
DEPENDENCY
Depend and set Actions for package obsoletion,
renaming, variants.
SUMMARY
Any remaining set Actions not listed above, such
as pkg.summary, pkg.description, etc.
'last' is a boolean value that indicates only the last entry
for each package on a per-publisher basis should be returned.
As long as the catalog has been saved since the last modifying
operation, or finalize() has has been called, this will also be
the newest version of the package.
'locales' is an optional set of locale names for which Actions
should be returned. The default is set(('C',)) if not provided.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pfmri' is an optional FMRI to limit the returned results to.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
try:
yield (r, entry,
excludes))
except KeyError:
"""A boolean value indicating whether the Catalog exists
on-disk."""
# If the Catalog attrs file exists on-disk,
# then the catalog does.
"""This function re-sorts the contents of the Catalog so that
version entries are in the correct order and sets the package
counts for the Catalog based on its current contents.
'pfmris' is an optional set of FMRIs that indicate what package
entries have been changed since this function was last called.
It is used to optimize the finalization process.
'pubs' is an optional set of publisher prefixes that indicate
what publisher has had package entries changed. It is used
to optimize the finalization process. This option has no effect
if 'pfmris' is also provided."""
"""A generator function that produces FMRIs as it iterates
over the contents of the catalog.
'last' is a boolean value that indicates only the last FMRI
for each package on a per-publisher basis should be returned.
As long as the catalog has been saved since the last modifying
operation, or finalize() has has been called, this will also be
the newest version of the package.
'objects' is an optional boolean value indicating whether
FMRIs should be returned as FMRI objects or as strings.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
if base is None:
# Catalog contains nothing.
# This construction is necessary to get python to
# return no results properly to callers expecting
# a generator function.
return iter(())
"""A generator function that produces tuples of (version,
fmris), where fmris is a of the fmris related to the
version, for the given package name.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
if base is None:
# Catalog contains nothing.
# This construction is necessary to get python to
# return no results properly to callers expecting
# a generator function.
return iter(())
"""Returns a dict containing the metadata for the specified
FMRI containing the requested information. If the specified
FMRI does not exist in the catalog, a value of None will be
returned.
'metadata' always contains the following information at a
minimum:
BASE
'metadata' will be populated with Manifest
signature data, if available, using key-value
pairs of the form 'signature-<name>': value.
'info_needed' is an optional list of one or more catalog
constants indicating the types of catalog data that will
be returned in 'metadata' in addition to the above:
DEPENDENCY
'metadata' will contain depend and set Actions
for package obsoletion, renaming, variants,
and facets stored in a list under the
key 'actions'.
SUMMARY
'metadata' will contain any remaining Actions
not listed above, such as pkg.summary,
pkg.description, etc. in a list under the key
'actions'.
'locales' is an optional set of locale names for which Actions
should be returned. The default is set(('C',)) if not provided.
"""
if k == "actions":
dest.setdefault(k, [])
dest[k] += v
elif k != "version":
dest[k] = v
parts = []
if base is None:
return
if not locales:
else:
# Always attempt to retrieve the BASE entry as FMRIs
# must be present in the BASE catalog part.
mdata = {}
if bentry is None:
return
if part is not None:
if part is None:
# Data not available for this
# locale.
continue
if entry is None:
# Part doesn't have this FMRI,
# so skip it.
continue
return mdata
locales=None):
"""A generator function that produces Actions as it iterates
over the catalog entry of the specified FMRI corresponding to
the requested information). If the catalog doesn't contain
any action data for the package entry, it will return an empty
iterator.
'excludes' is a list of variants which will be used to determine
what should be allowed by the actions generator in addition to
what is specified by 'info_needed'. If not provided, only
'info_needed' will determine what actions are returned.
'info_needed' is a set of one or more catalog constants
indicating the types of catalog data that will be returned
in 'actions' in addition to the above:
DEPENDENCY
Depend and set Actions for package obsoletion,
renaming, variants.
SUMMARY
Any remaining set Actions not listed above, such
as pkg.summary, pkg.description, etc.
'locales' is an optional set of locale names for which Actions
should be returned. The default is set(('C',)) if not provided.
"""
assert info_needed
if not locales:
else:
if entry is None:
try:
except KeyError:
return EmptyI
"""A generator function that yields tuples of the format
(var_name, variants); where var_name is the name of the
variant and variants is a list of the variants for that
name."""
if entry is None:
try:
except KeyError:
return
for a in actions:
if a.name != "set":
continue
continue
"""A generator function that yields tuples of the form (sig,
value) where 'sig' is the name of the signature, and 'value' is
the raw catalog value for the signature. Please note that the
data type of 'value' is dependent on the signature, so it may
be a string, list, dict, etc."""
if entry is None:
return (
if k.startswith("signature-")
)
"""A generator function that returns the variants for the
specified variant name. If no variants exist for the
specified name, None will be returned."""
# A package can only have one set of values
# for a single variant name, so return it.
return values
return None
"""A generator function that produces tuples of the form:
(
(
pub, - (string) the publisher of the package
stem, - (string) the name of the package
version - (string) the version of the package
),
states, - (list) states
attributes - (dict) package attributes
)
Results are always sorted by stem, publisher, and then in
descending version order.
'collect_attrs' is an optional boolean that indicates whether
all package attributes should be collected and returned in the
fifth element of the return tuple. If False, that element will
be an empty dictionary.
'matched' is an optional set to add matched patterns to.
'patterns' is an optional list of FMRI wildcard strings to
filter results by.
'pubs' is an optional list of publisher prefixes to restrict
the results to.
'unmatched' is an optional set to add unmatched patterns to.
'return_fmris' is an optional boolean value that indicates that
an FMRI object should be returned in place of the (pub, stem,
ver) tuple that is normally returned."""
# Each pattern in patterns can be a partial or full FMRI, so
# extract the individual components for use in filtering.
illegals = []
pat_tuples = {}
latest_pats = set()
patterns):
if error:
continue
# Duplicate patterns are ignored.
# A different form of the same pattern
# was specified already; ignore this
continue
# Track used patterns.
if illegals:
# Keep track of listed stems for all other packages on a
# per-publisher basis.
# Track matching patterns.
matched_pats = set()
pkg_matching_pats = None
# Need dependency and summary actions.
omit_package = None
# A newer version has already been listed, so
# any additional entries need to be marked for
# omission before continuing.
else:
pkg_matching_pats = set()
if not omit_package:
ever = None
if pat_pub is not None and \
# Publisher doesn't match.
if omit_package is None:
continue
# Stem doesn't match.
if omit_package is None:
omit_package = \
continue
"/" + pat_stem):
# Stem doesn't match.
if omit_package is None:
omit_package = \
continue
pat_stem):
# Stem doesn't match.
if omit_package is None:
omit_package = \
continue
if pat_ver is not None:
if ever is None:
# Avoid constructing a
# version object more
# than once for each
# entry.
if omit_package is None:
omit_package = \
continue
if pat in latest_pats and \
# Package allowed by pattern,
# but isn't the "latest"
# version.
if omit_package is None:
continue
# If this entry matched at least one
# pattern, then ensure it is returned.
if (matched is None and
unmatched is None):
# It's faster to stop as soon
# as a match is found.
break
# If caller has requested other match
# cases be returned, then all patterns
# must be tested for every entry. This
# is slower, so only done if necessary.
if omit_package:
# Package didn't match critera; skip it.
continue
# Collect attribute data if requested.
summ = None
if collect_attrs:
# use OrderedDict to get a deterministic output
lambda: OrderedDict([]))
else:
try:
for a in actions:
if a.name != "set":
continue
if collect_attrs:
# mods = frozenset(
# (k1, frozenset([k1_1, k1_2]))
# (k2, frozenset([k2_1, k2_2]))
# )
# will later be converted by the
# caller into a dict like:
# {
# k1: frozenset([k1_1, k1_2]),
# k2: frozenset([k2_1, k2_2])
# }
if k not in ("name", "value")
)
else:
if atname == "pkg.summary":
continue
if atname == "description":
if summ is not None:
continue
# Historical summary field.
if collect_attrs:
if mods not in \
attrs["pkg.summary"]:
attrs["pkg.summary"]\
else:
attrs["pkg.summary"]\
continue
if atname == "pkg.renamed":
if atvalue == "true":
continue
if atname == "pkg.obsolete":
if atvalue == "true":
continue
except api_errors.InvalidPackageErrors:
# Ignore errors for packages that have invalid
# or unsupported metadata.
if omit_package:
# Package didn't match criteria; skip it.
continue
# Only after all other filtering has been
# applied are the patterns that the package
# matched considered "matching".
# Return the requested package data.
if return_fmris:
else:
if matched is not None:
# Caller has requested that matched patterns be
# returned.
if unmatched is not None:
# Caller has requested that unmatched patterns be
# returned.
"""Given a user-specified list of FMRI pattern strings, return
a tuple of ('matching', 'references', 'unmatched'), where
matching is a dict of matching fmris, references is a dict of
the patterns indexed by matching FMRI, and unmatched is a set of
the patterns that did not match any FMRIs respectively:
{
pkgname: [fmri1, fmri2, ...],
pkgname: [fmri1, fmri2, ...],
...
}
{
fmri1: [pat1, pat2, ...],
fmri2: [pat1, pat2, ...],
...
}
set(['unmatched1', 'unmatchedN'])
'patterns' is the list of package patterns to match.
Constraint used is always AUTO as per expected UI behavior when
determining successor versions.
Note that patterns starting w/ pkg:/ require an exact match;
patterns containing '*' will using fnmatch rules; the default
trailing match rules are used for remaining patterns.
Exactly duplicated patterns are ignored.
Routine raises PackageMatchErrors if errors occur: it is
illegal to specify multiple different patterns that match the
same package name. Only patterns that contain wildcards are
allowed to match multiple packages.
"""
# problems we check for
illegals = []
multimatch = []
multispec = []
pat_data = []
wildcard_patterns = set()
# Each pattern in patterns can be a partial or full FMRI, so
# extract the individual components for use in filtering.
latest_pats = set()
patterns):
if error:
continue
# Duplicate patterns are ignored.
# A different form of the same pattern
# was specified already; ignore this
continue
# Track used patterns.
if illegals:
# Create a dictionary of patterns, with each value being a
# dictionary of pkg names & fmris that match that pattern.
continue # name doesn't match
continue # version doesn't match
# specified pubs
# conflict
continue
[]).append(f)
# Discard all but the newest version of each match.
if latest_pats:
# Rebuild ret based on latest version of every package.
latest = {}
nret = {}
for p in patterns:
if p not in latest_pats or not ret[p]:
continue
nret[p] = {}
None)
# Not the newest.
continue
# Allow for multiple
# FMRIs of the same
# latest version.
f)
continue
# Assign new version of ret and discard latest list.
del latest
# Determine match failures.
matchdict = {}
for p in patterns:
if l == 0: # no matches at all
elif l > 1 and p not in wildcard_patterns:
# multiple matches
multimatch.append((p, [
for n in ret[p]
]))
else:
# single match or wildcard
# for each matching package name
if multimatch:
raise api_errors.PackageMatchErrors(
# Group the matching patterns by package name and allow multiple
# fmri matches.
proposed_dict = {}
# construct references so that we can know which pattern
# generated which fmris...
references = dict([
(f, p)
for f in flist
])
"""Returns a generator of tuples of the form (pub,
package_count, package_version_count). 'pub' is the publisher
prefix, 'package_count' is the number of unique packages for the
publisher, and 'package_version_count' is the number of unique
package versions for the publisher.
"""
if base is None:
# Catalog contains nothing.
# This construction is necessary to get python to
# return no results properly to callers expecting
# a generator function.
return iter(())
"""Returns the CatalogPart object for the named catalog part.
'must_exist' is an optional boolean value that indicates that
the catalog part must already exist in-memory or on-disk, if
not a value of None will be returned."""
# First, check if the part has already been cached, and if so,
# return it.
if part is not None:
return part
return
# If the caller said the part must_exist, then it must already
# be part of the catalog attributes to be valid.
return
# Next, since the part hasn't been cached, create an object
# for it and add it to catalog attributes.
# This is a double-check for the client case where
# there is a part that is known to the catalog but
# that the client has purposefully not retrieved.
# (Think locale specific data.)
return
# Add a new entry to the catalog attributes for this new
# part since it didn't exist previously.
}
return part
"""Returns a list of the catalog files needed to update
the existing catalog parts, based on the contents of the
catalog.attrs file in the directory indicated by 'path'.
A value of None will be returned if the the catalog has
not been modified, while an empty list will be returned
if no catalog parts need to be updated, but the catalog
itself has changed."""
# No updates needed (not even to attrs), so return None.
return None
# It's very likely that the catalog has been recreated
# or this is a completely different catalog than was
# expected. In either case, an update isn't possible.
# No updates needed (not even to attrs), so return None.
return None
# First, verify that all of the catalog parts the client has
# still exist. If they no longer exist, the catalog is no
# longer valid and cannot be updated.
parts = {}
# Part hasn't changed.
continue
# The last component of the update name is the locale.
# Now check to see if an update log is still offered for
# the last time this catalog part was updated. If it
# does not, then an incremental update cannot be safely
# performed since updates may be missing.
# XXX in future, add current locale to this. For now, just
# ensure that all of the locales of parts that were changed
# and exist on-disk are included.
# Now determine if there are any new parts for this locale that
# this version of the API knows how to use that the client
# doesn't already have.
continue
# The last component of the name is the locale.
continue
# Currently, only these parts are used by the client,
# so only they need to be retrieved.
# If a new part has been added for the current
# locale, then incremental updates can't be
# performed since updates for this locale can
# only be applied to parts that already exist.
if not parts:
# No updates needed to catalog parts on-disk, but
# catalog has changed.
return []
elif not incremental:
# Since an incremental update cannot be performed,
# just return the updated parts for retrieval.
return updates
# Finally, determine the update logs needed based on the catalog
# parts that need updating on a per-locale basis.
# Determine the newest catalog part for a given locale,
# this will be used to determine which update logs are
# needed for an incremental update.
last_lm = None
continue
# The last component of the update name is the
# locale.
# This update log doesn't apply to the
# locale being evaluated for updates.
continue
# Older or same as newest catalog part
# for this locale; so skip.
continue
# If this updatelog was changed after the
# newest catalog part for this locale, then
# it is needed to update one or more catalog
# parts for this locale.
# Ensure updates are in chronological ascending order.
"""Returns a set containing the names of all the packages in
the Catalog.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
if base is None:
# Catalog contains nothing.
return set()
"""The number of unique packages in the catalog."""
"""The number of unique package versions in the catalog."""
"""A dict containing the list of CatalogParts that the catalog
is composed of along with information about each part."""
"""A generator function that produces package tuples of the form
(pub, stem) as it iterates over the contents of the catalog.
'pubs' is an optional list that contains the prefixes of the
publishers to restrict the results to."""
if base is None:
# Catalog contains nothing.
# This construction is necessary to get python to
# return no results properly to callers expecting
# a generator function.
return iter(())
"""Returns a set containing the prefixes of all the publishers
in the Catalog."""
if base is None:
# Catalog contains nothing.
return set()
"""Remove a package and its metadata."""
try:
# The package has to be removed from every known part.
entries = {}
# Use the same operation time and date for all
# operations so that the last modification times
# of all catalog parts and update logs will be
# synchronized.
if part is None:
continue
if pkg_entry is None:
# Entry should exist in at least
# the base part.
raise api_errors.UnknownCatalogEntry(
# Skip; package's presence is optional
# in other parts.
continue
if self.log_updates:
finally:
"""Finalize current state and save to file if possible."""
try:
finally:
"""Returns a dict of the files the catalog is composed of along
with the last known signatures of each if they are available."""
sigs = {
}
try:
except IndexError:
# Not a signature entry.
continue
return sigs
"""A generator function that produces FMRI tuples as it
iterates over the contents of the catalog.
'last' is a boolean value that indicates only the last FMRI
tuple for each package on a per-publisher basis should be
returned. As long as the catalog has been saved since the
last modifying operation, or finalize() has has been called,
this will also be the newest version of the package.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
if base is None:
# Catalog contains nothing.
# This construction is necessary to get python to
# return no results properly to callers expecting
# a generator function.
return iter(())
"""A generator function that produces tuples of the format
((pub, stem, version), entry, actions) as it iterates over
the contents of the catalog (where 'metadata' is a dict
containing the requested information).
'metadata' always contains the following information at a
minimum:
BASE
'metadata' will be populated with Manifest
signature data, if available, using key-value
pairs of the form 'signature-<name>': value.
'info_needed' is an optional list of one or more catalog
constants indicating the types of catalog data that will
be returned in 'metadata' in addition to the above:
DEPENDENCY
'metadata' will contain depend and set Actions
for package obsoletion, renaming, variants,
and facets stored in a list under the
key 'actions'.
SUMMARY
'metadata' will contain any remaining Actions
not listed above, such as pkg.summary,
pkg.description, etc. in a list under the key
'actions'.
'last' is a boolean value that indicates only the last entry
for each package on a per-publisher basis should be returned.
As long as the catalog has been saved since the last modifying
operation, or finalize() has has been called, this will also be
the newest version of the package.
'locales' is an optional set of locale names for which Actions
should be returned. The default is set(('C',)) if not provided.
'ordered' is an optional boolean value that indicates that
results should sorted by stem and then by publisher and
be in descending version order. If False, results will be
in a ascending version order on a per-publisher, per-stem
basis.
'pubs' is an optional list of publisher prefixes to restrict
the results to."""
"""A dict containing the list of known updates for the catalog
along with information about each update."""
ver=None):
"""Updates the metadata stored in a package's BASE catalog
record for the specified package. Cannot be used when read_only
or log_updates is enabled; should never be used with a Catalog
intended for incremental update usage.
'metadata' must be a dict of additional metadata to store with
the package's BASE record.
'pfmri' is the FMRI of the package to update the entry for.
'pub' is the publisher of the package.
'stem' is the stem of the package.
'ver' is the version string of the package.
'pfmri' or 'pub', 'stem', and 'ver' must be provided.
"""
if base is None:
if not pfmri:
# get_entry returns the actual catalog entry, so updating it
# simply requires reassignment.
if entry is None:
if not pfmri:
if metadata is None:
if "metadata" in entry:
del entry["metadata"]
return
"last-modified": op_time
}
"""Verifies whether the signatures for the contents of the
catalog match the current signature data. Raises the
exception named 'BadCatalogSignatures' on failure."""
sigs = {}
continue
if not sigs:
# Allow validate() to perform its own fallback
# logic if signature data isn't available.
return None
return sigs
if part is None:
# Part does not exist; no validation needed.
continue
if ulog is None:
# Update does not exist; no validation needed.
continue
doc="A UTC datetime object indicating the last time the catalog "
"was modified.")
# Methods used by external callers
"""Convert the catalog part named by filename into the correct
type of Catalog object and then call its validate method to ensure
that is contents are self-consistent."""
catobj = None
else:
else:
# Unrecognized.
# With the else case above, this should never be None.
assert catobj
# Methods used by Catalog classes.
"""Take datetime object dt, and convert it to a ts in ISO-8601
format. """
"""Take datetime object dt, and convert it to a ts in ISO-8601
basic format. """
# Assume UTC.
val += "Z"
return val
"""Take datetime object dt, and convert it to a ts in ISO-8601
basic partial format. """
# Drop the minutes and seconds portion.
# Assume UTC.
val += "Z"
return val
def now_to_basic_ts():
"""Returns the current UTC time as timestamp in ISO-8601 basic
format."""
def now_to_update_ts():
"""Returns the current UTC time as timestamp in ISO-8601 basic
partial format."""
"""Take timestamp ts in ISO-8601 format, and convert it to a
datetime object."""
# usec is not in the string if 0
try:
except ValueError:
usec = 0
"""Take timestamp ts in ISO-8601 basic format, and convert it to a
datetime object."""
# usec is not in the string if 0
try:
except ValueError:
usec = 0