'Update the catalog as necessary.
"selections" (optional) is a list of string of the same form
as to `.get_lib()\' -- used here to filter the catalogs
that we consider for updating.
"progress_cb" (optional) is a callable that is called as
follows to show the progress of the update:
progress_cb(<desc>, <value>)
where <desc> is a short string describing the current step
and <value> is an integer between 0 and 100 indicating the
level of completeness.'

def update(self, selections=None, progress_cb=None):

    self._lock.acquire()
    try:
        self._have_updated_at_least_once = True
        if progress_cb:
            try:
                progress_cb('Determining    necessary    catalog    updates...', 5)
            except:
                log.exception('error    in    progress_cb    (ignoring)')
        res_name_from_res_path = dict(((p, v[2]) for (p, v) in self.res_index.items()))
        todos = []
        log.info('updating    %s:    %d    catalog    dir(s)', self, len(self.catalog_dirs))
        for catalog_info in self.avail_catalogs(selections):
            cix_path = catalog_info['cix_path']
            res = AreaResource(cix_path)
            try:
                (res_id, last_updated, name, res_data) = self.res_index[res.area_path]
            except KeyError:
                todos.append(('add', res, catalog_info['name']))
            else:
                mtime = os.stat(cix_path).st_mtime
                if (last_updated != mtime):
                    todos.append(('update', res, catalog_info['name']))
                del res_name_from_res_path[res.area_path]
        for (res_area_path, res_name) in res_name_from_res_path.items():
            try:
                todos.append(('remove', AreaResource(res_area_path), res_name))
            except ValueError as ex:
                pass
        if (selections is not None):
            selection_from_selector = self._selection_from_selector(selections)
            before = todos[:]
            todos = [todo for todo in todos if ((todo[2].lower() in selection_from_selector) or (normpath(normcase(todo[1].path)) in selection_from_selector))]
        if (not todos):
            return
        for (i, (action, res, name)) in enumerate(todos):
            log.debug("%s    `%s'    catalog    (%s)", action, name, res)
            try:
                if (action == 'add'):
                    desc = ("Adding    '%s'    API    catalog" % basename(res.subpath))
                    if progress_cb:
                        try:
                            progress_cb(desc, (5 + ((95 / len(todos)) * i)))
                        except:
                            log.exception('error    in    progress_cb    (ignoring)')
                    else:
                        self.db.report_event(desc)
                    self._add_res(res)
                elif (action == 'remove'):
                    desc = ("Removing    '%s'    API    catalog" % basename(res.subpath))
                    if progress_cb:
                        try:
                            progress_cb(desc, (5 + ((95 / len(todos)) * i)))
                        except:
                            log.exception('error    in    progress_cb    (ignoring)')
                    else:
                        self.db.report_event(desc)
                    self._remove_res(res)
                elif (action == 'update'):
                    desc = ("Updating    '%s'    API    catalog" % basename(res.subpath))
                    if progress_cb:
                        try:
                            progress_cb(desc, (5 + ((95 / len(todos)) * i)))
                        except:
                            log.exception('error    in    progress_cb    (ignoring)')
                    else:
                        self.db.report_event(desc)
                    self._remove_res(res)
                    self._add_res(res)
            except DatabaseError as ex:
                log.warn(('%s    (skipping)' % ex))
        if progress_cb:
            try:
                progress_cb('Saving    catalog    indices...', 95)
            except:
                log.exception('error    in    progress_cb    (ignoring)')
        self._res_ids_from_selector_cache = None
        if (self._res_index is not None):
            self.db.save_pickle(join(self.base_dir, 'res_index'), self._res_index)
        if (self._blob_index is not None):
            self.db.save_pickle(join(self.base_dir, 'blob_index'), self._blob_index)
        if (self._toplevelname_index is not None):
            self.db.save_pickle(join(self.base_dir, 'toplevelname_index'), self._toplevelname_index)
        if (self._toplevelprefix_index is not None):
            self.db.save_pickle(join(self.base_dir, 'toplevelprefix_index'), self._toplevelprefix_index)
    finally:
        self._lock.release()
