Skip to content

Commit

Permalink
Rewrite path attributes when cloning static frame
Browse files Browse the repository at this point in the history
Since paths are stored internally as relative to
the RMF file, we need to rewrite them if we copy
them into an RMF file in a different directory.
  • Loading branch information
benmwebb committed Jun 6, 2023
1 parent 6f9ca5e commit 474139e
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 1 deletion.
78 changes: 77 additions & 1 deletion src/internal/clone_shared_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* \file RMF/internal/SharedData.h
* \brief Handle read/write of Model data from/to files.
*
* Copyright 2007-2022 IMP Inventors. All rights reserved.
* Copyright 2007-2023 IMP Inventors. All rights reserved.
*
*/

Expand All @@ -13,10 +13,12 @@
#include "RMF/log.h"
#include "RMF/internal/shared_data_ranges.h"
#include "RMF/internal/SharedData.h"
#include "RMF/internal/paths.h"
#include "shared_data_maps.h"
#include "shared_data_equality.h"

#include <boost/range/distance.hpp>
#include <boost/algorithm/string/predicate.hpp>

RMF_ENABLE_WARNINGS

Expand Down Expand Up @@ -167,12 +169,86 @@ void clone_values_category(SDA* sda, Category cata, SDB* sdb, Category catb,
RMF_FOREACH_TYPE(RMF_CLONE_VALUES);
}

// Return true iff an attribute contains a filesystem path.
// Unfortunately RMF stores both paths and non-path strings in String(s)
// attributes (and we can't easily add a separate Path(s) type without
// breaking backwards compatibility), so we do this based on the name
// of the attribute. The convention is to end path attribute names in
// "filename" or "filenames" but we have to also include a few that
// predate this convention.
inline bool is_string_key_path(std::string name) {
return boost::algorithm::ends_with(name, "filename") ||
boost::algorithm::ends_with(name, "filenames") ||
name == "cluster density" ||
name == "image files" ||
name == "path";
}

// Rewrite a String node attribute containing a relative path
template <class SDB>
void rewrite_node_path(SDB *sdb, NodeID n, ID<StringTraits> k,
StringTraits::ReturnType s,
const std::string &from_path,
const std::string &to_path) {
sdb->set_static_value(
n, k, get_relative_path(to_path, get_absolute_path(from_path, s)));
}

// Rewrite a Strings node attribute containing relative paths
template <class SDB>
void rewrite_node_path(SDB *sdb, NodeID n, ID<StringsTraits> k,
StringsTraits::ReturnType s,
const std::string &from_path,
const std::string &to_path) {
for (std::string &es : s) {
es = get_relative_path(to_path, get_absolute_path(from_path, es));
}
sdb->set_static_value(n, k, s);
}

// Rewrite all relative paths (in String or Strings attributes) in the
// given category, that were relative to from_path, to be relative to to_path
template <class Traits, class SDB>
void rewrite_paths_type(SDB *sdb, Category catb,
const std::string &from_path,
const std::string &to_path) {
std::vector<ID<Traits>> keysb = sdb->get_keys(catb, Traits());
for (auto &k : keysb) {
if (is_string_key_path(sdb->get_name(k))) {
for(NodeID n : get_nodes(sdb)) {
typename Traits::ReturnType s = sdb->get_static_value(n, k);
if (!Traits::get_is_null_value(s)) {
rewrite_node_path(sdb, n, k, s, from_path, to_path);
}
}
}
}
}

// Rewrite all path attributes, that were relative to sba, to be
// relative to sdb
template <class SDA, class SDB>
void rewrite_relative_paths(SDA* sda, SDB* sdb) {
std::string from_path = sda->get_file_path();
std::string to_path = sdb->get_file_path();
for (Category catb : sdb->get_categories()) {
// Both String and Strings attributes can contain paths
rewrite_paths_type<StringTraits>(sdb, catb, from_path, to_path);
rewrite_paths_type<StringsTraits>(sdb, catb, from_path, to_path);
}
}

template <class SDA, class SDB>
void clone_static_data(SDA* sda, SDB* sdb) {
for(Category cata : sda->get_categories()) {
Category catb = sdb->get_category(sda->get_name(cata));
clone_values_category(sda, cata, sdb, catb, StaticValues());
}
// Path attributes are relative to sda, so rewrite (if necessary) to
// be relative to sdb
if (!get_is_same_base_path(sda->get_file_path(), sdb->get_file_path())) {
rewrite_relative_paths(sda, sdb);
}
}

template <class SDA, class SDB>
Expand Down
37 changes: 37 additions & 0 deletions test/test_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,43 @@ def test_parent_path(self):
# API should return an absolute path
self.assertEqual(prov.get_filename(), ap)

def test_clone_rewrite_path(self):
"""Test that clone rewrites relative paths"""
tmpdir = RMF._get_temporary_file_path('test_clone_rewrite_path')
subdir = os.path.join(tmpdir, 'sub1')
subsubdir = os.path.join(subdir, 'sub2')
os.makedirs(subsubdir)
rmf = RMF.create_rmf_file(os.path.join(subdir, 'test.rmf3'))
key = self.get_filename_key(rmf)
node, prov = self.make_test_node(rmf)

ap = os.path.join(subsubdir, 'foo.pdb')
prov.set_filename(ap)

if sys.platform != 'win32':
# On Mac/Linux, internally, a relative path should be stored
self.assertEqual(node.get_value(key), 'sub2/foo.pdb')
# API should return an absolute path
self.assertEqual(prov.get_filename(), ap)
del rmf, node, prov

inr = RMF.open_rmf_file_read_only(os.path.join(subdir, 'test.rmf3'))
outr = RMF.create_rmf_file(os.path.join(subsubdir, 'test.rmf3'))
RMF.clone_file_info(inr, outr)
RMF.clone_hierarchy(inr, outr)
RMF.clone_static_frame(inr, outr)
rt = outr.get_root_node()
f = RMF.StructureProvenanceFactory(outr)
node, = rt.get_children()
self.assertTrue(f.get_is(node))
prov = f.get(node)
if sys.platform != 'win32':
# New file should have a different relative path
key = self.get_filename_key(outr)
self.assertEqual(node.get_value(key), 'foo.pdb')
# API should return the same absolute path
self.assertEqual(prov.get_filename(), ap)


if __name__ == '__main__':
unittest.main()

0 comments on commit 474139e

Please sign in to comment.