|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | + |
| 3 | +# This work was created by participants in the DataONE project, and is |
| 4 | +# jointly copyrighted by participating institutions in DataONE. For |
| 5 | +# more information on DataONE, see our web site at http://dataone.org. |
| 6 | +# |
| 7 | +# Copyright 2009-2016 DataONE |
| 8 | +# |
| 9 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 10 | +# you may not use this file except in compliance with the License. |
| 11 | +# You may obtain a copy of the License at |
| 12 | +# |
| 13 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 14 | +# |
| 15 | +# Unless required by applicable law or agreed to in writing, software |
| 16 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 17 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 18 | +# See the License for the specific language governing permissions and |
| 19 | +# limitations under the License. |
| 20 | +"""Utilities for working with revision / obsolescence chains |
| 21 | +""" |
| 22 | +from __future__ import absolute_import |
| 23 | + |
| 24 | +import d1_common.util |
| 25 | +import d1_common.xml |
| 26 | + |
| 27 | + |
| 28 | +def get_identifiers(sysmeta_pyxb): |
| 29 | + """Return: (pid, sid, obsoletes_pid, obsoleted_by_pid) |
| 30 | + """ |
| 31 | + pid = d1_common.xml.get_opt_val(sysmeta_pyxb, 'identifier') |
| 32 | + sid = d1_common.xml.get_opt_val(sysmeta_pyxb, 'seriesId') |
| 33 | + obsoletes_pid = d1_common.xml.get_opt_val(sysmeta_pyxb, 'obsoletes') |
| 34 | + obsoleted_by_pid = d1_common.xml.get_opt_val(sysmeta_pyxb, 'obsoletedBy') |
| 35 | + return pid, sid, obsoletes_pid, obsoleted_by_pid |
| 36 | + |
| 37 | + |
| 38 | +def topological_sort(unsorted_dict): |
| 39 | + """Sort objects by dependency |
| 40 | +
|
| 41 | + {unconnected_dict} is a dict of PID to obsoleted PID. |
| 42 | +
|
| 43 | + Return: |
| 44 | + sorted_list: A list of PIDs ordered so that all PIDs that obsolete an object |
| 45 | + are listed after the object they obsolete. |
| 46 | + unconnected_dict: A dict of PID to obsoleted PID of any objects that could not |
| 47 | + be added to a revision chain. |
| 48 | +
|
| 49 | + {obsoletes_dict} is modified by the sort and on return holds any items that |
| 50 | + could not be sorted. These items will have obsoletes PIDs that directly or |
| 51 | + indirectly reference a PID that could not be sorted. |
| 52 | +
|
| 53 | + The sort works by repeatedly iterating over an unsorted list of PIDs and |
| 54 | + moving PIDs to the sorted list as they become available. A PID is available to |
| 55 | + be moved to the sorted list if it does not obsolete a PID or if the PID it |
| 56 | + obsoletes is already in the sorted list. |
| 57 | + """ |
| 58 | + sorted_list = [] |
| 59 | + sorted_set = set() |
| 60 | + found = True |
| 61 | + unconnected_dict = unsorted_dict.copy() |
| 62 | + while found: |
| 63 | + found = False |
| 64 | + for pid, obsoletes_pid in unconnected_dict.items(): |
| 65 | + if obsoletes_pid is None or obsoletes_pid in sorted_set: |
| 66 | + found = True |
| 67 | + sorted_list.append(pid) |
| 68 | + sorted_set.add(pid) |
| 69 | + del unconnected_dict[pid] |
| 70 | + return sorted_list, unconnected_dict |
| 71 | + |
| 72 | + |
| 73 | +def get_pids_in_revision_chain(client, did): |
| 74 | + """Given a SID or a PID of any object in a chain, return a list of all PIDs in |
| 75 | + the chain. The returned list is in the same order as the chain. The initial |
| 76 | + PID is typically obtained by resolving a SID. If the given PID is not in a |
| 77 | + chain, a list containing the single object is returned. |
| 78 | + """ |
| 79 | + |
| 80 | + def req(p): |
| 81 | + return d1_common.xml.get_req_val(p) |
| 82 | + |
| 83 | + def opt(p, a): |
| 84 | + return d1_common.xml.get_opt_val(p, a) |
| 85 | + |
| 86 | + sysmeta_pyxb = client.getSystemMetadata(did) |
| 87 | + # Walk to tail |
| 88 | + while opt(sysmeta_pyxb, 'obsoletes'): |
| 89 | + sysmeta_pyxb = client.getSystemMetadata(opt(sysmeta_pyxb, 'obsoletes')) |
| 90 | + chain_pid_list = [req(sysmeta_pyxb.identifier)] |
| 91 | + # Walk from tail to head, recording traversed PIDs |
| 92 | + while opt(sysmeta_pyxb, 'obsoletedBy'): |
| 93 | + sysmeta_pyxb = client.getSystemMetadata(opt(sysmeta_pyxb, 'obsoletedBy')) |
| 94 | + chain_pid_list.append(req(sysmeta_pyxb.identifier)) |
| 95 | + return chain_pid_list |
0 commit comments