Add search by digest if search by name fails (#2935).
authorCarl Hetherington <cth@carlh.net>
Sun, 19 Jan 2025 19:50:35 +0000 (20:50 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 19 Jan 2025 19:50:35 +0000 (20:50 +0100)
src/lib/find_missing.cc
test/find_missing_test.cc

index 73eb504606fbf90143355a6cb4882ea96748d3d3..65776d02e9b1fe6e0c37d0505b7a694cbc520a75 100644 (file)
@@ -58,23 +58,68 @@ search_by_name(Replacements& replacement_paths, boost::filesystem::path director
 }
 
 
+static
+void
+search_by_digest(Replacements& replacement_paths, boost::filesystem::path directory, int depth = 0)
+{
+       boost::system::error_code ec;
+       for (auto candidate: dcp::filesystem::directory_iterator(directory, ec)) {
+               if (dcp::filesystem::is_regular_file(candidate.path())) {
+                       auto const candidate_digest = simple_digest({candidate.path()});
+                       for (auto& replacement: replacement_paths) {
+                               DCPOMATIC_ASSERT(replacement.first->number_of_paths() == 1)
+                               if (replacement.first->digest() == candidate_digest) {
+                                       replacement.second = { candidate.path() };
+                               }
+                       }
+               } else if (dcp::filesystem::is_directory(candidate.path()) && depth <= 2) {
+                       search_by_digest(replacement_paths, candidate, depth + 1);
+               }
+       }
+}
+
+
 void
 dcpomatic::find_missing (vector<shared_ptr<Content>> content_to_fix, boost::filesystem::path clue)
 {
        using namespace boost::filesystem;
 
-       Replacements replacement_paths;
+       Replacements name_replacement_paths;
        for (auto content: content_to_fix) {
-               replacement_paths[content] = content->paths();
+               name_replacement_paths[content] = content->paths();
        }
 
-       search_by_name(replacement_paths, is_directory(clue) ? clue : clue.parent_path());
+       /* Look for replacements with the same filename */
+       search_by_name(name_replacement_paths, is_directory(clue) ? clue : clue.parent_path());
 
+       /* Fix any content that can be fixed with those, making a note of those that cannot */
+       Replacements digest_replacement_paths;
        for (auto content: content_to_fix) {
-               auto const& repl = replacement_paths[content];
+               auto const& repl = name_replacement_paths[content];
                bool const replacements_exist = std::find_if(repl.begin(), repl.end(), [](path p) { return !exists(p); }) == repl.end();
-               if (replacements_exist && simple_digest(replacement_paths[content]) == content->digest()) {
+               if (replacements_exist && simple_digest(name_replacement_paths[content]) == content->digest()) {
                        content->set_paths (repl);
+               } else {
+                       /* Put it on the list to look for by digest, if possible */
+                       if (content->number_of_paths() == 1) {
+                               digest_replacement_paths[content] = name_replacement_paths[content];
+                       }
+               }
+       }
+
+       if (!digest_replacement_paths.empty()) {
+               /* Search for content with just one path by digest */
+               search_by_digest(digest_replacement_paths, is_directory(clue) ? clue : clue.parent_path());
+
+               for (auto content: content_to_fix) {
+                       auto iter = digest_replacement_paths.find(content);
+                       if (iter != digest_replacement_paths.end()) {
+                               auto const& repl = iter->second;
+                               bool const replacements_exist = std::find_if(repl.begin(), repl.end(), [](path p) { return !exists(p); }) == repl.end();
+                               if (replacements_exist) {
+                                       content->set_paths(repl);
+                               }
+                       }
                }
        }
 }
index c85ddea7f10c06c0a619635d4d3a59f1ac82af7c..809a8f864a7d9b709a51f3b1d2f353b5ea3ba841 100644 (file)
@@ -30,6 +30,7 @@
 
 
 using std::make_shared;
+using std::shared_ptr;
 using std::string;
 
 
@@ -154,3 +155,45 @@ BOOST_AUTO_TEST_CASE (find_missing_test_with_multiple_files_one_incorrect)
        }
 }
 
+
+BOOST_AUTO_TEST_CASE(find_missing_test_with_rename)
+{
+       using namespace boost::filesystem;
+
+       auto name = string{"find_missing_test_with_rename"};
+
+       /* Make a directory with some content */
+       auto content_dir = path("build/test") / path(name + "_content");
+       remove_all(content_dir);
+       create_directories(content_dir);
+       copy_file("test/data/flat_red.png", content_dir / "A.png");
+       copy_file("test/data/flat_red.png", content_dir / "B.png");
+       copy_file("test/data/flat_red.png", content_dir / "C.png");
+
+       /* Make a film with that content */
+       auto film = new_test_film(name + "_film", {
+               content_factory(content_dir / "A.png")[0],
+               content_factory(content_dir / "B.png")[0],
+               content_factory(content_dir / "C.png")[0]
+               });
+       film->write_metadata();
+
+       /* Rename one of the files */
+       rename(content_dir / "C.png", content_dir / "bogus.png");
+
+       /* That should make one of the content paths invalid */
+       auto content_list = film->content();
+       int const valid = std::count_if(content_list.begin(), content_list.end(), [](shared_ptr<const Content> content) {
+               return content->paths_valid();
+       });
+       BOOST_CHECK_EQUAL(valid, 2);
+
+       /* Fix the missing files and check the result */
+       dcpomatic::find_missing(content_list, content_dir / "bogus.png");
+
+       for (auto content: content_list) {
+               BOOST_CHECK(content->paths_valid());
+       }
+
+}
+