Extract and improve code to find missing files (#1940).
authorCarl Hetherington <cth@carlh.net>
Sat, 25 Dec 2021 01:05:54 +0000 (02:05 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 25 Dec 2021 01:07:56 +0000 (02:07 +0100)
src/lib/find_missing.cc [new file with mode: 0644]
src/lib/find_missing.h [new file with mode: 0644]
src/lib/wscript
src/wx/content_menu.cc
src/wx/content_menu.h
test/find_missing_test.cc [new file with mode: 0644]
test/wscript

diff --git a/src/lib/find_missing.cc b/src/lib/find_missing.cc
new file mode 100644 (file)
index 0000000..3d61e74
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+    Copyright (C) 2021 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "content.h"
+#include "find_missing.h"
+#include "util.h"
+#include <boost/filesystem.hpp>
+
+
+using std::map;
+using std::shared_ptr;
+using std::vector;
+
+
+typedef map<shared_ptr<Content>, vector<boost::filesystem::path>> 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<shared_ptr<Content>> 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 (file)
index 0000000..6755b5d
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+    Copyright (C) 2021 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <boost/filesystem.hpp>
+#include <memory>
+#include <vector>
+
+
+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<std::shared_ptr<Content>> content, boost::filesystem::path clue);
+
+
+}
+
index 8278ec4a1187f60bf44861d5b2b29c9f994f3536..0a0790fcd16f2ec8096c5f6b6de2d266dada0a14 100644 (file)
@@ -97,6 +97,7 @@ sources = """
           file_group.cc
           file_log.cc
           filter_graph.cc
+          find_missing.cc
           ffmpeg.cc
           ffmpeg_audio_stream.cc
           ffmpeg_content.cc
index 22802cc0595968f11e592f9c7b0e25fa79457b1c..4c0afdf3a05475968dffc26eca6f1b4995360a9d 100644 (file)
@@ -343,35 +343,11 @@ ContentMenu::find_missing ()
                d->Destroy ();
        }
 
-       list<shared_ptr<Content>> content;
-
-       if (r == wxID_OK) {
-               if (dc) {
-                       content.push_back (make_shared<DCPContent>(path));
-               } else {
-                       content = content_factory (path);
-               }
-       }
-
-       if (content.empty ()) {
+       if (r == wxID_CANCEL) {
                return;
        }
 
-       for (auto i: content) {
-               auto j = make_shared<ExamineContentJob>(film, i);
-
-               j->Finished.connect (
-                       bind (
-                               &ContentMenu::maybe_found_missing,
-                               this,
-                               std::weak_ptr<Job> (j),
-                               std::weak_ptr<Content> (_content.front ()),
-                               std::weak_ptr<Content> (i)
-                               )
-                       );
-
-               JobManager::instance()->add (j);
-       }
+       dcpomatic::find_missing (film->content(), path);
 }
 
 void
@@ -387,26 +363,6 @@ ContentMenu::re_examine ()
        }
 }
 
-void
-ContentMenu::maybe_found_missing (weak_ptr<Job> j, weak_ptr<Content> oc, weak_ptr<Content> 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 ()
index 8a3548419aac8d7a9f5067b4a834004fb772f6b8..03f657d3882f1618d1cb9b5e264e811a90fe2315 100644 (file)
@@ -51,7 +51,6 @@ private:
        void ov ();
        void set_dcp_settings ();
        void remove ();
-       void maybe_found_missing (std::weak_ptr<Job>, std::weak_ptr<Content>, std::weak_ptr<Content>);
        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 (file)
index 0000000..1bc4bef
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+    Copyright (C) 2021 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#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 <boost/filesystem.hpp>
+#include <boost/test/unit_test.hpp>
+
+
+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<DCPContent>(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<DCPContent>(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());
+       }
+}
+
index 847cab3909fe368cee87372fceecddaff2b63f50..81315d4b75c3710e121ae00120f8f497a134dcbc 100644 (file)
@@ -85,6 +85,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
                  hints_test.cc