search_storage.py revision 959
429N/A#!/usr/bin/python
429N/A#
429N/A# CDDL HEADER START
429N/A#
429N/A# The contents of this file are subject to the terms of the
429N/A# Common Development and Distribution License (the "License").
429N/A# You may not use this file except in compliance with the License.
429N/A#
429N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
429N/A# or http://www.opensolaris.org/os/licensing.
429N/A# See the License for the specific language governing permissions
429N/A# and limitations under the License.
429N/A#
429N/A# When distributing Covered Code, include this CDDL HEADER in each
429N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
429N/A# If applicable, add the following below this CDDL HEADER, with the
429N/A# fields enclosed by brackets "[]" replaced with your own identifying
429N/A# information: Portions Copyright [yyyy] [name of copyright owner]
429N/A#
429N/A# CDDL HEADER END
429N/A#
941N/A
941N/A#
941N/A# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
429N/A# Use is subject to license terms.
941N/A#
429N/A
429N/Aimport os
429N/Aimport errno
429N/Aimport time
516N/Aimport sha
546N/Aimport urllib
429N/A
429N/Aimport pkg.fmri as fmri
429N/Aimport pkg.search_errors as search_errors
429N/Aimport pkg.portable as portable
429N/A
941N/AFAST_ADD = 'fast_add.v1'
941N/AFAST_REMOVE = 'fast_remove.v1'
941N/AMANIFEST_LIST = 'manf_list.v1'
546N/AFULL_FMRI_FILE = 'full_fmri_list'
941N/AMAIN_FILE = 'main_dict.ascii.v2'
546N/ABYTE_OFFSET_FILE = 'token_byte_offset.v1'
546N/AFULL_FMRI_HASH_FILE = 'full_fmri_list.hash'
546N/A
516N/Adef consistent_open(data_list, directory, timeout = 1):
429N/A """Opens all data holders in data_list and ensures that the
429N/A versions are consistent among all of them.
429N/A It retries several times in case a race condition between file
429N/A migration and open is encountered.
429N/A Note: Do not set timeout to be 0. It will cause an exception to be
429N/A immediately raised.
429N/A
429N/A """
429N/A missing = None
429N/A cur_version = None
429N/A
429N/A start_time = time.time()
429N/A
429N/A while cur_version == None and missing != True:
441N/A # The assignments to cur_version and missing cannot be
441N/A # placed here. They must be reset prior to breaking out of the
441N/A # for loop so that the while loop condition will be true. They
441N/A # cannot be placed after the for loop since that path is taken
441N/A # when all files are missing or opened successfully.
429N/A if timeout != None and ((time.time() - start_time) > timeout):
429N/A raise search_errors.InconsistentIndexException(
429N/A directory)
429N/A for d in data_list:
429N/A # All indexes must have the same version and all must
429N/A # either be present or absent for a successful return.
429N/A # If one of these conditions is not met, the function
429N/A # tries again until it succeeds or the time spent in
429N/A # in the function is greater than timeout.
429N/A try:
429N/A f = os.path.join(directory, d.get_file_name())
429N/A fh = open(f, 'rb')
429N/A # If we get here, then the current index file
429N/A # is present.
429N/A if missing == None:
429N/A missing = False
429N/A elif missing:
429N/A for dl in data_list:
429N/A dl.close_file_handle()
429N/A missing = None
429N/A cur_version = None
441N/A break
429N/A d.set_file_handle(fh, f)
546N/A version_tmp = fh.readline()
429N/A version_num = \
429N/A int(version_tmp.split(' ')[1].rstrip('\n'))
429N/A # Read the version. If this is the first file,
429N/A # set the expected version otherwise check that
429N/A # the version matches the expected version.
429N/A if cur_version == None:
429N/A cur_version = version_num
429N/A elif not (cur_version == version_num):
429N/A # Got inconsistent versions, so close
429N/A # all files and try again.
429N/A for d in data_list:
429N/A d.close_file_handle()
429N/A missing = None
429N/A cur_version = None
441N/A break
429N/A except IOError, e:
429N/A if e.errno == errno.ENOENT:
429N/A # If the index file is missing, ensure
429N/A # that previous files were missing as
429N/A # well. If not, try again.
429N/A if missing == False:
429N/A for d in data_list:
429N/A d.close_file_handle()
429N/A missing = None
429N/A cur_version = None
441N/A break
429N/A missing = True
429N/A else:
429N/A for d in data_list:
429N/A d.close_file_handle()
429N/A raise
429N/A if missing:
429N/A assert cur_version == None
429N/A # The index is missing (ie, no files were present).
429N/A return None
429N/A else:
429N/A assert cur_version is not None
429N/A return cur_version
429N/A
429N/A
429N/Aclass IndexStoreBase(object):
429N/A """Base class for all data storage used by the indexer and
429N/A queryEngine. All members must have a file name and maintain
429N/A an internal file handle to that file as instructed by external
429N/A calls.
429N/A """
429N/A
429N/A def __init__(self, file_name):
429N/A self._name = file_name
429N/A self._file_handle = None
429N/A self._file_path = None
429N/A self._size = None
429N/A self._mtime = None
429N/A
429N/A def get_file_name(self):
429N/A return self._name
429N/A
429N/A def set_file_handle(self, f_handle, f_path):
429N/A if self._file_handle:
429N/A raise RuntimeError("setting an extant file handle, "
429N/A "must close first, fp is: " + f_path)
429N/A else:
429N/A self._file_handle = f_handle
429N/A self._file_path = f_path
429N/A
429N/A def get_file_path(self):
429N/A return self._file_path
429N/A
941N/A def __copy__(self):
941N/A return self.__class__(self._name)
941N/A
429N/A def close_file_handle(self):
429N/A """Closes the file handle and clears it so that it cannot
429N/A be reused.
429N/A """
429N/A if self._file_handle:
429N/A self._file_handle.close()
429N/A self._file_handle = None
429N/A
429N/A def _protected_write_dict_file(self, path, version_num, iterable):
429N/A """Writes the dictionary in the expected format.
429N/A Note: Only child classes should call this method.
429N/A """
429N/A version_string = "VERSION: "
429N/A file_handle = open(os.path.join(path, self._name), 'wb')
429N/A file_handle.write(version_string + str(version_num) + "\n")
429N/A for name in iterable:
429N/A file_handle.write(str(name) + "\n")
429N/A file_handle.close()
429N/A
429N/A def should_reread(self):
429N/A """This method uses the modification time and the file size
429N/A to (heuristically) determine whether the file backing this
429N/A storage has changed since it was last read.
429N/A """
429N/A stat_info = os.stat(self._file_path)
429N/A if self._mtime != stat_info.st_mtime or \
429N/A self._size != stat_info.st_size:
621N/A self._mtime = stat_info.st_mtime
621N/A self._size = stat_info.st_size
429N/A return True
429N/A return False
429N/A
429N/A def open(self, directory):
429N/A """This uses consistent open to ensure that the version line
429N/A processing is done consistently and that only a single function
429N/A actually opens files stored using this class.
429N/A """
429N/A return consistent_open([self], directory)
429N/A
429N/A
429N/Aclass IndexStoreMainDict(IndexStoreBase):
429N/A """Class for representing the main dictionary file
429N/A """
429N/A # Here is an example of a line from the main dictionary, it is
429N/A # explained below:
429N/A # %gconf.xml (5,3,65689 => 249,202) (5,3,65690 => 249,202)
429N/A # (5,3,65691 => 249,202) (5,3,65692 => 249,202)
429N/A #
429N/A # The main dictionary has a more complicated format. Each line begins
429N/A # with a search token (%gconf.xml) followed by a list of mappings. Each
429N/A # mapping takes a token_type, action, and keyvalue tuple ((5,3,65689),
429N/A # (5,3,65690), (5,3,65691), (5,3,65692)) to a list of pkg-stem, version
429N/A # pairs (249,202) in which the token is found in an action with
429N/A # token_type, action, and keyvalues matching the tuple. Further
429N/A # compaction is gained by storing everything but the token as an id
429N/A # which the other dictionaries can turn into human-readable content.
429N/A #
429N/A # In short, the definition of a main dictionary entry is:
429N/A # Note: "(", ")", and "=>" actually appear in the file
429N/A # "[", "]", and "+" are used to specify pattern
429N/A # token [(token_type_id, action_id, keyval_id => [pkg_stem_id,version_id ]+)]+
429N/A
429N/A def __init__(self, file_name):
429N/A IndexStoreBase.__init__(self, file_name)
429N/A self._old_suffix = None
429N/A
429N/A def write_dict_file(self, path, version_num):
429N/A """This class relies on external methods to write the file.
429N/A Making this empty call to protected_write_dict_file allows the
429N/A file to be set up correctly with the version number stored
429N/A correctly.
429N/A """
429N/A IndexStoreBase._protected_write_dict_file(self, path,
429N/A version_num, [])
429N/A
429N/A def get_file_handle(self):
429N/A """Return the file handle. Note that doing
429N/A anything other than sequential reads or writes
429N/A to or from this file_handle may result in unexpected
429N/A behavior. In short, don't use seek.
429N/A """
429N/A return self._file_handle
429N/A
429N/A @staticmethod
941N/A def __parse_main_dict_line_help(split_chars, unquote_list, line):
941N/A if not split_chars:
941N/A if not line:
941N/A raise se.EmptyMainDictLine(split_chars, unquote_list)
941N/A elif not unquote_list:
941N/A raise se.EmptyUnquoteList(split_chars, line)
941N/A else:
941N/A assert len(unquote_list) == 1
941N/A if unquote_list[0]:
941N/A return urllib.unquote(line)
941N/A else:
941N/A return line
941N/A else:
941N/A cur_char = split_chars[0]
941N/A tmp = line.split(cur_char)
941N/A if unquote_list[0]:
941N/A header = urllib.unquote(tmp[0])
941N/A else:
941N/A header = tmp[0]
941N/A return (header, [
941N/A IndexStoreMainDict.__parse_main_dict_line_help(
941N/A split_chars[1:], unquote_list[1:], x)
941N/A for x
941N/A in tmp[1:]])
941N/A
941N/A @staticmethod
429N/A def parse_main_dict_line(line):
429N/A """Parses one line of a main dictionary file.
429N/A Changes to this function must be paired with changes to
429N/A write_main_dict_line below.
429N/A """
941N/A
429N/A line = line.rstrip('\n')
941N/A return IndexStoreMainDict.__parse_main_dict_line_help(
941N/A [" ", "!", "@", "#", ","],
941N/A [True, False, False, True, False, False], line)
429N/A
429N/A @staticmethod
941N/A def __write_main_dict_line_help(file_handle, sep_chars, quote, entries):
941N/A assert sep_chars
941N/A if not isinstance(entries, tuple):
941N/A assert len(sep_chars) == 1
941N/A file_handle.write(sep_chars[0])
941N/A if quote[0]:
941N/A file_handle.write(urllib.quote(str(entries)))
941N/A else:
941N/A file_handle.write(str(entries))
941N/A return
941N/A header, entries = entries
941N/A file_handle.write(sep_chars[0])
941N/A if quote[0]:
941N/A file_handle.write(urllib.quote(str(header)))
941N/A else:
941N/A file_handle.write(str(header))
941N/A for e in entries:
941N/A IndexStoreMainDict.__write_main_dict_line_help(
941N/A file_handle, sep_chars[1:], quote[1:], e)
941N/A
941N/A @staticmethod
941N/A def write_main_dict_line(file_handle, token, lst):
429N/A """Paired with parse_main_dict_line above. Writes
429N/A a line in a main dictionary file in the appropriate format.
429N/A """
941N/A IndexStoreMainDict.__write_main_dict_line_help(file_handle,
941N/A ["", " ", "!", "@", "#", ","],
941N/A [True, False, False, True, False, False], (token, lst))
429N/A file_handle.write("\n")
429N/A
941N/A @staticmethod
941N/A def __transform_main_dict_line_help(sep_chars, quote, entries):
941N/A assert sep_chars
941N/A ret = [sep_chars[0]]
941N/A if not isinstance(entries, tuple):
941N/A assert len(sep_chars) == 1
941N/A if quote[0]:
941N/A ret.append(urllib.quote(str(entries)))
941N/A else:
941N/A ret.append(str(entries))
941N/A return ret
941N/A header, entries = entries
941N/A if quote[0]:
941N/A ret.append(urllib.quote(str(header)))
941N/A else:
941N/A ret.append(str(header))
941N/A for e in entries:
941N/A tmp = \
941N/A IndexStoreMainDict.__transform_main_dict_line_help(
941N/A sep_chars[1:], quote[1:], e)
941N/A ret.extend(tmp)
941N/A
941N/A return ret
941N/A
941N/A @staticmethod
941N/A def transform_main_dict_line(token, lst):
941N/A """Paired with parse_main_dict_line above. Writes
941N/A a line in a main dictionary file in the appropriate format.
941N/A """
941N/A tmp = IndexStoreMainDict.__transform_main_dict_line_help(
941N/A ["", " ", "!", "@", "#", ","],
941N/A [True, False, False, True, False, False], (token, lst))
941N/A tmp.append("\n")
941N/A return "".join(tmp)
941N/A
429N/A def count_entries_removed_during_partial_indexing(self):
429N/A """Returns the number of entries removed during a second phase
429N/A of indexing.
429N/A """
429N/A # This returns 0 because this class is not responsible for
429N/A # storing anything in memory.
429N/A return 0
429N/A
429N/A def shift_file(self, use_dir, suffix):
429N/A """Moves the existing file with self._name in directory
429N/A use_dir to a new file named self._name + suffix in directory
429N/A use_dir. If it has done this previously, it removes the old
429N/A file it moved. It also opens the newly moved file and uses
429N/A that as the file for its file handle.
429N/A """
429N/A assert self._file_handle is None
429N/A orig_path = os.path.join(use_dir, self._name)
429N/A new_path = os.path.join(use_dir, self._name + suffix)
429N/A portable.rename(orig_path, new_path)
429N/A tmp_name = self._name
429N/A self._name = self._name + suffix
429N/A self.open(use_dir)
429N/A self._name = tmp_name
429N/A if self._old_suffix is not None:
429N/A os.remove(os.path.join(use_dir, self._old_suffix))
429N/A self._old_suffix = self._name + suffix
429N/A
429N/A
429N/Aclass IndexStoreListDict(IndexStoreBase):
429N/A """Used when both a list and a dictionary are needed to
429N/A store the information. Used for bidirectional lookup when
429N/A one item is an int (an id) and the other is not (an entity). It
429N/A maintains a list of empty spots in the list so that adding entities
429N/A can take advantage of unused space. It encodes empty space as a blank
429N/A line in the file format and '' in the internal list.
429N/A """
429N/A
429N/A def __init__(self, file_name, build_function=None):
429N/A IndexStoreBase.__init__(self, file_name)
429N/A self._list = []
429N/A self._dict = {}
429N/A self._next_id = 0
429N/A self._list_of_empties = []
429N/A self._build_func = build_function
429N/A self._line_cnt = 0
429N/A
429N/A def add_entity(self, entity, is_empty):
429N/A """Adds an entity consistently to the list and dictionary
429N/A allowing bidirectional lookup.
429N/A """
429N/A assert (len(self._list) == self._next_id)
429N/A if self._list_of_empties and not is_empty:
429N/A use_id = self._list_of_empties.pop(0)
429N/A assert use_id <= len(self._list)
429N/A if use_id == len(self._list):
429N/A self._list.append(entity)
429N/A self._next_id += 1
429N/A else:
429N/A self._list[use_id] = entity
429N/A else:
429N/A use_id = self._next_id
429N/A self._list.append(entity)
429N/A self._next_id += 1
429N/A if not(is_empty):
429N/A self._dict[entity] = use_id
429N/A assert (len(self._list) == self._next_id)
429N/A return use_id
429N/A
429N/A def remove_id(self, in_id):
429N/A """deletes in_id from the list and the dictionary """
429N/A entity = self._list[in_id]
429N/A self._list[in_id] = ""
429N/A self._dict[entity] = ""
429N/A
429N/A def remove_entity(self, entity):
429N/A """deletes the entity from the list and the dictionary """
429N/A in_id = self._dict[entity]
429N/A self._dict[entity] = ""
429N/A self._list[in_id] = ""
429N/A
429N/A def get_id(self, entity):
429N/A """returns the id of entity """
429N/A return self._dict[entity]
429N/A
429N/A def get_id_and_add(self, entity):
429N/A """Adds entity if it's not previously stored and returns the
429N/A id for entity.
429N/A """
429N/A # This code purposefully reimplements add_entity
429N/A # code. Replacing the function calls to has_entity, add_entity,
429N/A # and get_id with direct access to the data structure gave a
429N/A # speed up of a factor of 4. Because this is a very hot path,
429N/A # the tradeoff seemed appropriate.
429N/A
429N/A if not self._dict.has_key(entity):
429N/A assert (len(self._list) == self._next_id)
429N/A if self._list_of_empties:
429N/A use_id = self._list_of_empties.pop(0)
429N/A assert use_id <= len(self._list)
429N/A if use_id == len(self._list):
429N/A self._list.append(entity)
429N/A self._next_id += 1
429N/A else:
429N/A self._list[use_id] = entity
429N/A else:
429N/A use_id = self._next_id
429N/A self._list.append(entity)
429N/A self._next_id += 1
429N/A self._dict[entity] = use_id
429N/A assert (len(self._list) == self._next_id)
429N/A return self._dict[entity]
429N/A
429N/A def get_entity(self, in_id):
429N/A """return the entity in_id maps to """
429N/A return self._list[in_id]
429N/A
429N/A def has_entity(self, entity):
429N/A """check if entity is in storage """
429N/A return self._dict.has_key(entity)
429N/A
429N/A def has_empty(self):
429N/A """Check if the structure has any empty elements which
429N/A can be filled with data.
429N/A """
429N/A return (len(self._list_of_empties) > 0)
429N/A
429N/A def get_next_empty(self):
429N/A """returns the next id which maps to no element """
429N/A return self._list_of_empties.pop()
429N/A
429N/A def write_dict_file(self, path, version_num):
429N/A """Passes self._list to the parent class to write to a file.
429N/A """
429N/A IndexStoreBase._protected_write_dict_file(self, path,
429N/A version_num,
429N/A self._list)
429N/A
429N/A def read_dict_file(self):
429N/A """Reads in a dictionary previously stored using the above
429N/A call
429N/A """
429N/A assert self._file_handle
429N/A if self.should_reread():
429N/A self._dict.clear()
429N/A self._list = []
429N/A for i, line in enumerate(self._file_handle):
429N/A # A blank line means that id can be reused.
429N/A tmp = line.rstrip('\n')
429N/A if line == '\n':
429N/A self._list_of_empties.append(i)
429N/A else:
429N/A if self._build_func:
429N/A tmp = self._build_func(tmp)
429N/A self._dict[tmp] = i
429N/A self._list.append(tmp)
429N/A self._line_cnt = i + 1
429N/A self._next_id = i + 1
429N/A return self._line_cnt
429N/A
429N/A def count_entries_removed_during_partial_indexing(self):
429N/A """Returns the number of entries removed during a second phase
429N/A of indexing.
429N/A """
429N/A return len(self._list)
429N/A
429N/Aclass IndexStoreDict(IndexStoreBase):
429N/A """Class used when only entity -> id lookup is needed
429N/A """
429N/A
429N/A def __init__(self, file_name):
429N/A IndexStoreBase.__init__(self, file_name)
429N/A self._dict = {}
429N/A self._next_id = 0
429N/A
429N/A def get_dict(self):
429N/A return self._dict
429N/A
429N/A def get_entity(self, in_id):
429N/A return self._dict[in_id]
429N/A
429N/A def has_entity(self, entity):
429N/A return self._dict.has_key(entity)
429N/A
429N/A def read_dict_file(self):
429N/A """Reads in a dictionary stored in line number -> entity
429N/A format
429N/A """
429N/A if self.should_reread():
429N/A self._dict.clear()
429N/A for line_cnt, line in enumerate(self._file_handle):
429N/A line = line.rstrip('\n')
429N/A self._dict[line_cnt] = line
429N/A
941N/A def matching_read_dict_file(self, in_set, update=False):
429N/A """If it's necessary to reread the file, it rereads the
429N/A file. It matches the line it reads against the contents of
429N/A in_set. If a match is found, the entry on the line is stored
429N/A for later use, otherwise the line is skipped. When all items
429N/A in in_set have been matched, the method is done and returns.
429N/A """
941N/A if update or self.should_reread():
941N/A if not update:
941N/A self._dict.clear()
429N/A match_cnt = 0
429N/A max_match = len(in_set)
941N/A self._file_handle.seek(0)
941N/A # skip the version line
941N/A self._file_handle.next()
429N/A for i, line in enumerate(self._file_handle):
429N/A if i in in_set:
429N/A match_cnt += 1
429N/A line = line.rstrip('\n')
429N/A self._dict[i] = line
429N/A if match_cnt >= max_match:
429N/A break
429N/A
429N/A def count_entries_removed_during_partial_indexing(self):
429N/A """Returns the number of entries removed during a second phase
429N/A of indexing.
429N/A """
429N/A return len(self._dict)
429N/A
429N/Aclass IndexStoreDictMutable(IndexStoreBase):
429N/A """Dictionary which allows dynamic update of its storage
429N/A """
429N/A
429N/A def __init__(self, file_name):
429N/A IndexStoreBase.__init__(self, file_name)
429N/A self._dict = {}
429N/A
429N/A def get_dict(self):
429N/A return self._dict
429N/A
429N/A def has_entity(self, entity):
429N/A return self._dict.has_key(entity)
429N/A
429N/A def get_id(self, entity):
429N/A return self._dict[entity]
429N/A
499N/A def get_keys(self):
499N/A return self._dict.keys()
499N/A
546N/A @staticmethod
546N/A def __unquote(str):
546N/A if str[0] == "1":
546N/A return urllib.unquote(str[1:])
546N/A else:
546N/A return str[1:]
546N/A
546N/A @staticmethod
546N/A def __quote(str):
546N/A if " " in str:
546N/A return "1" + urllib.quote(str)
546N/A else:
546N/A return "0" + str
546N/A
429N/A def read_dict_file(self):
429N/A """Reads in a dictionary stored in with an entity
429N/A and its number on each line.
429N/A """
429N/A if self.should_reread():
429N/A self._dict.clear()
429N/A for line in self._file_handle:
546N/A res = line.split(" ")
546N/A token = self.__unquote(res[0])
429N/A offset = int(res[1])
429N/A self._dict[token] = offset
429N/A
429N/A def open_out_file(self, use_dir, version_num):
429N/A """Opens the output file for this class and prepares it
429N/A to be written via write_entity.
429N/A """
546N/A self.write_dict_file(use_dir, version_num)
429N/A self._file_handle = open(os.path.join(use_dir, self._name),
941N/A 'ab', buffering=131072)
429N/A
429N/A def write_entity(self, entity, my_id):
429N/A """Writes the entity out to the file with my_id """
429N/A assert self._file_handle is not None
941N/A self._file_handle.write(self.__quote(str(entity)) + " " +
941N/A str(my_id) + "\n")
429N/A
429N/A def write_dict_file(self, path, version_num):
429N/A """ Generates an iterable list of string representations of
429N/A the dictionary that the parent's protected_write_dict_file
429N/A function can call.
429N/A """
429N/A IndexStoreBase._protected_write_dict_file(self, path,
429N/A version_num, [])
429N/A
429N/A def count_entries_removed_during_partial_indexing(self):
429N/A """Returns the number of entries removed during a second phase
429N/A of indexing.
429N/A """
429N/A return 0
429N/A
516N/Aclass IndexStoreSetHash(IndexStoreBase):
516N/A def __init__(self, file_name):
516N/A IndexStoreBase.__init__(self, file_name)
516N/A self.hash_val = sha.new().hexdigest()
516N/A
516N/A def set_hash(self, vals):
516N/A """Set the has value."""
516N/A self.hash_val = self.calc_hash(vals)
516N/A
516N/A def calc_hash(self, vals):
516N/A """Calculate the hash value of the sorted members of vals."""
516N/A vl = list(vals)
516N/A vl.sort()
516N/A shasum = sha.new()
516N/A for v in vl:
516N/A shasum.update(v)
516N/A return shasum.hexdigest()
516N/A
516N/A def write_dict_file(self, path, version_num):
516N/A """Write self.hash_val out to a line in a file """
516N/A IndexStoreBase._protected_write_dict_file(self, path,
516N/A version_num, [self.hash_val])
516N/A
516N/A def read_dict_file(self):
516N/A """Process a dictionary file written using the above method
516N/A """
959N/A if self.should_reread():
959N/A sp = self._file_handle.tell()
959N/A res = 0
959N/A for res, line in enumerate(self._file_handle):
959N/A assert res < 1
959N/A self.hash_val = line.rstrip()
959N/A self._file_handle.seek(sp)
959N/A return res
941N/A
516N/A def check_against_file(self, vals):
516N/A """Check the hash value of vals against the value stored
516N/A in the file for this object."""
959N/A self.read_dict_file()
516N/A incoming_hash = self.calc_hash(vals)
941N/A if self.hash_val != incoming_hash:
941N/A raise search_errors.IncorrectIndexFileHash(
941N/A self.hash_val, incoming_hash)
516N/A
516N/A def count_entries_removed_during_partial_indexing(self):
516N/A """Returns the number of entries removed during a second phase
516N/A of indexing."""
516N/A return 0
516N/A
429N/Aclass IndexStoreSet(IndexStoreBase):
429N/A """Used when only set membership is desired.
429N/A This is currently designed for exclusive use
429N/A with storage of fmri.PkgFmris. However, that impact
429N/A is only seen in the read_and_discard_matching_from_argument
429N/A method.
429N/A """
429N/A def __init__(self, file_name):
429N/A IndexStoreBase.__init__(self, file_name)
429N/A self._set = set()
429N/A
429N/A def get_set(self):
429N/A return self._set
429N/A
429N/A def add_entity(self, entity):
429N/A self._set.add(entity)
429N/A
429N/A def remove_entity(self, entity):
429N/A """Remove entity purposfully assumes that entity is
429N/A already in the set to be removed. This is useful for
429N/A error checking and debugging.
429N/A """
429N/A self._set.remove(entity)
429N/A
429N/A def has_entity(self, entity):
429N/A return (entity in self._set)
429N/A
429N/A def write_dict_file(self, path, version_num):
429N/A """Write each member of the set out to a line in a file """
429N/A IndexStoreBase._protected_write_dict_file(self, path,
429N/A version_num, self._set)
429N/A
429N/A def read_dict_file(self):
429N/A """Process a dictionary file written using the above method
429N/A """
429N/A assert self._file_handle
429N/A res = 0
429N/A if self.should_reread():
429N/A self._set.clear()
429N/A for i, line in enumerate(self._file_handle):
429N/A line = line.rstrip('\n')
429N/A assert i == len(self._set)
429N/A self.add_entity(line)
429N/A res = i + 1
429N/A return res
429N/A
429N/A def read_and_discard_matching_from_argument(self, fmri_set):
429N/A """Reads the file and removes all frmis in the file
429N/A from fmri_set.
429N/A """
429N/A if self._file_handle:
429N/A for line in self._file_handle:
429N/A f = fmri.PkgFmri(line)
429N/A fmri_set.discard(f)
429N/A
429N/A def count_entries_removed_during_partial_indexing(self):
429N/A """Returns the number of entries removed during a second phase
429N/A of indexing."""
429N/A return len(self._set)