From: Carl Hetherington Date: Sat, 25 Dec 2021 01:05:54 +0000 (+0100) Subject: Extract and improve code to find missing files (#1940). X-Git-Tag: v2.16.11~33 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=266e3f1b80d1263ff3cc8b3afaecd6ca1f88983b Extract and improve code to find missing files (#1940). --- diff --git a/src/lib/find_missing.cc b/src/lib/find_missing.cc new file mode 100644 index 000000000..3d61e74bd --- /dev/null +++ b/src/lib/find_missing.cc @@ -0,0 +1,75 @@ +/* + Copyright (C) 2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "content.h" +#include "find_missing.h" +#include "util.h" +#include + + +using std::map; +using std::shared_ptr; +using std::vector; + + +typedef map, vector> Replacements; + + +static +void +search (Replacements& replacement_paths, boost::filesystem::path directory, int depth = 0) +{ + for (auto candidate: boost::filesystem::directory_iterator(directory)) { + if (boost::filesystem::is_regular_file(candidate.path())) { + for (auto& replacement: replacement_paths) { + for (auto& path: replacement.second) { + if (!boost::filesystem::exists(path) && path.filename() == candidate.path().filename()) { + path = candidate.path(); + } + } + } + } else if (boost::filesystem::is_directory(candidate.path()) && depth <= 2) { + search (replacement_paths, candidate, depth + 1); + } + } +} + + +void +dcpomatic::find_missing (vector> content_to_fix, boost::filesystem::path clue) +{ + using namespace boost::filesystem; + + Replacements replacement_paths; + for (auto content: content_to_fix) { + replacement_paths[content] = content->paths(); + } + + search (replacement_paths, is_directory(clue) ? clue : clue.parent_path()); + + for (auto content: content_to_fix) { + auto const& repl = 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()) { + content->set_paths (repl); + } + } +} diff --git a/src/lib/find_missing.h b/src/lib/find_missing.h new file mode 100644 index 000000000..6755b5dd1 --- /dev/null +++ b/src/lib/find_missing.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include +#include +#include + + +class Content; + + +namespace dcpomatic { + + +/** Try to resolve some missing content file paths using a clue. On return + * any content whose files were found will have been updated. + * + * @param content Content, some of which may have missing files. + * @param clue Path to a file which gives a clue about where the missing files might be. + */ +void find_missing (std::vector> content, boost::filesystem::path clue); + + +} + diff --git a/src/lib/wscript b/src/lib/wscript index 30240cd11..22fb144bc 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -98,6 +98,7 @@ sources = """ file_group.cc file_log.cc filter_graph.cc + find_missing.cc ffmpeg.cc ffmpeg_audio_stream.cc ffmpeg_content.cc diff --git a/src/wx/content_menu.cc b/src/wx/content_menu.cc index 18ce64475..a80a5fc82 100644 --- a/src/wx/content_menu.cc +++ b/src/wx/content_menu.cc @@ -354,35 +354,11 @@ ContentMenu::find_missing () d->Destroy (); } - list> content; - - if (r == wxID_OK) { - if (dc) { - content.push_back (make_shared(path)); - } else { - content = content_factory (path); - } - } - - if (content.empty ()) { + if (r == wxID_CANCEL) { return; } - for (auto i: content) { - auto j = make_shared(film, i); - - j->Finished.connect ( - bind ( - &ContentMenu::maybe_found_missing, - this, - std::weak_ptr (j), - std::weak_ptr (_content.front ()), - std::weak_ptr (i) - ) - ); - - JobManager::instance()->add (j); - } + dcpomatic::find_missing (film->content(), path); } void @@ -398,26 +374,6 @@ ContentMenu::re_examine () } } -void -ContentMenu::maybe_found_missing (weak_ptr j, weak_ptr oc, weak_ptr nc) -{ - auto job = j.lock (); - if (!job || !job->finished_ok ()) { - return; - } - - auto old_content = oc.lock (); - auto new_content = nc.lock (); - DCPOMATIC_ASSERT (old_content); - DCPOMATIC_ASSERT (new_content); - - if (new_content->digest() != old_content->digest()) { - error_dialog (0, _("The content file(s) you specified are not the same as those that are missing. Either try again with the correct content file or remove the missing content.")); - return; - } - - old_content->set_paths (new_content->paths()); -} void ContentMenu::kdm () diff --git a/src/wx/content_menu.h b/src/wx/content_menu.h index 6e1641e66..ebbdf5b60 100644 --- a/src/wx/content_menu.h +++ b/src/wx/content_menu.h @@ -61,7 +61,6 @@ private: void ov (); void set_dcp_settings (); void remove (); - void maybe_found_missing (std::weak_ptr, std::weak_ptr, std::weak_ptr); void cpl_selected (wxCommandEvent& ev); wxMenu* _menu; diff --git a/test/find_missing_test.cc b/test/find_missing_test.cc new file mode 100644 index 000000000..1bc4bef01 --- /dev/null +++ b/test/find_missing_test.cc @@ -0,0 +1,155 @@ +/* + Copyright (C) 2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "lib/content.h" +#include "lib/content_factory.h" +#include "lib/dcp_content.h" +#include "lib/film.h" +#include "lib/find_missing.h" +#include "test.h" +#include +#include + + +using std::make_shared; +using std::string; + + +BOOST_AUTO_TEST_CASE (find_missing_test_with_single_files) +{ + using namespace boost::filesystem; + + auto name = string{"find_missing_test_with_single_files"}; + + /* 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_film2 (name + "_film", { + content_factory(content_dir / "A.png").front(), + content_factory(content_dir / "B.png").front(), + content_factory(content_dir / "C.png").front() + }); + film->write_metadata (); + + /* Move the content somewhere eles */ + auto moved = path("build/test") / path(name + "_moved"); + remove_all (moved); + rename (content_dir, moved); + + /* That should make the content paths invalid */ + for (auto content: film->content()) { + BOOST_CHECK (!content->paths_valid()); + } + + /* Fix the missing files and check the result */ + dcpomatic::find_missing (film->content(), moved / "A.png"); + + for (auto content: film->content()) { + BOOST_CHECK (content->paths_valid()); + } +} + + +BOOST_AUTO_TEST_CASE (find_missing_test_with_multiple_files) +{ + using namespace boost::filesystem; + + auto name = string{"find_missing_test_with_multiple_files"}; + + /* Copy an arbitrary DCP into a test directory */ + auto content_dir = path("build/test") / path(name + "_content"); + remove_all (content_dir); + create_directories (content_dir); + for (auto ref: directory_iterator("test/data/scaling_test_133_185")) { + copy (ref, content_dir / ref.path().filename()); + } + + /* Make a film containing that DCP */ + auto film = new_test_film2 (name + "_film", { make_shared(content_dir) }); + film->write_metadata (); + + /* Move the DCP's content elsewhere */ + auto moved = path("build/test") / path(name + "_moved"); + remove_all (moved); + rename (content_dir, moved); + + /* That should make the content paths invalid */ + for (auto content: film->content()) { + BOOST_CHECK (!content->paths_valid()); + } + + /* Fix the missing files and check the result */ + dcpomatic::find_missing (film->content(), moved / "foo"); + + for (auto content: film->content()) { + BOOST_CHECK (content->paths_valid()); + } +} + + +BOOST_AUTO_TEST_CASE (find_missing_test_with_multiple_files_one_incorrect) +{ + using namespace boost::filesystem; + + auto name = string{"find_missing_test_with_multiple_files_one_incorrect"}; + + /* Copy an arbitrary DCP into a test directory */ + auto content_dir = path("build/test") / path(name + "_content"); + remove_all (content_dir); + create_directories (content_dir); + for (auto ref: directory_iterator("test/data/scaling_test_133_185")) { + copy (ref, content_dir / ref.path().filename()); + } + + /* Make a film containing that DCP */ + auto film = new_test_film2 (name + "_film", { make_shared(content_dir) }); + film->write_metadata (); + + /* Move the DCP's content elsewhere */ + auto moved = path("build/test") / path(name + "_moved"); + remove_all (moved); + rename (content_dir, moved); + + /* Corrupt one of the files in the moved content, so that it should not be found in the find_missing + * step + */ + remove (moved / "cpl_80daeb7a-57d8-4a70-abeb-cd92ddac1527.xml"); + copy ("test/data/scaling_test_133_185/ASSETMAP.xml", moved / "cpl_80daeb7a-57d8-4a70-abeb-cd92ddac1527.xml"); + + /* The film's contents should be invalid */ + for (auto content: film->content()) { + BOOST_CHECK (!content->paths_valid()); + } + + dcpomatic::find_missing (film->content(), moved / "foo"); + + /* And even after find_missing there should still be missing content */ + for (auto content: film->content()) { + BOOST_CHECK (!content->paths_valid()); + } +} + diff --git a/test/wscript b/test/wscript index 4e28f1740..1555f2f59 100644 --- a/test/wscript +++ b/test/wscript @@ -89,6 +89,7 @@ def build(bld): file_log_test.cc file_naming_test.cc film_metadata_test.cc + find_missing_test.cc frame_interval_checker_test.cc frame_rate_test.cc guess_crop_test.cc