Various improvements to dcpomatic_kdm.
authorCarl Hetherington <cth@carlh.net>
Thu, 10 Oct 2013 11:14:52 +0000 (12:14 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 10 Oct 2013 11:14:52 +0000 (12:14 +0100)
ChangeLog
src/lib/kdm.cc [new file with mode: 0644]
src/lib/kdm.h [new file with mode: 0644]
src/lib/wscript
src/tools/dcpomatic.cc
src/tools/dcpomatic_kdm.cc
src/tools/wscript

index f60dcbcc4c8ce5ac376c10eda67ff5e4997995b9..eaaefd1ed6886ac8ab0601206708c3704f19cee1 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,7 @@
 2013-10-10  Carl Hetherington  <cth@carlh.net>
 
+       * Various improvements to dcpomatic_kdm.
+
        * libdcp fix to incorrect signature digests.
 
 2013-10-09  Carl Hetherington  <cth@carlh.net>
diff --git a/src/lib/kdm.cc b/src/lib/kdm.cc
new file mode 100644 (file)
index 0000000..3503306
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+#include <quickmail.h>
+#include <zip.h>
+#include <libdcp/kdm.h>
+#include "kdm.h"
+#include "cinema.h"
+#include "exceptions.h"
+#include "util.h"
+#include "film.h"
+#include "config.h"
+
+using std::list;
+using std::string;
+using boost::shared_ptr;
+
+struct ScreenKDM
+{
+       ScreenKDM (shared_ptr<Screen> s, libdcp::KDM k)
+               : screen (s)
+               , kdm (k)
+       {}
+       
+       shared_ptr<Screen> screen;
+       libdcp::KDM kdm;
+};
+
+struct CinemaKDMs
+{
+       shared_ptr<Cinema> cinema;
+       list<ScreenKDM> screen_kdms;
+
+       void make_zip_file (boost::filesystem::path zip_file) const
+       {
+               int error;
+               struct zip* zip = zip_open (zip_file.string().c_str(), ZIP_CREATE | ZIP_EXCL, &error);
+               if (!zip) {
+                       if (error == ZIP_ER_EXISTS) {
+                               throw FileError ("ZIP file already exists", zip_file);
+                       }
+                       throw FileError ("could not create ZIP file", zip_file);
+               }
+               
+               list<shared_ptr<string> > kdm_strings;
+               
+               for (list<ScreenKDM>::const_iterator i = screen_kdms.begin(); i != screen_kdms.end(); ++i) {
+                       shared_ptr<string> kdm (new string (i->kdm.as_xml ()));
+                       kdm_strings.push_back (kdm);
+                       
+                       struct zip_source* source = zip_source_buffer (zip, kdm->c_str(), kdm->length(), 0);
+                       if (!source) {
+                               throw StringError ("could not create ZIP source");
+                       }
+                       
+                       string const name = tidy_for_filename (i->screen->cinema->name) + "_" +
+                               tidy_for_filename (i->screen->name) + ".kdm.xml";
+                       
+                       if (zip_add (zip, name.c_str(), source) == -1) {
+                               throw StringError ("failed to add KDM to ZIP archive");
+                       }
+               }
+               
+               if (zip_close (zip) == -1) {
+                       throw StringError ("failed to close ZIP archive");
+               }
+       }
+};
+
+/* Not complete but sufficient for our purposes (we're using
+   ScreenKDM in a list where all the screens will be unique).
+*/
+bool
+operator== (ScreenKDM const & a, ScreenKDM const & b)
+{
+       return a.screen == b.screen;
+}
+
+static list<ScreenKDM>
+make_screen_kdms (shared_ptr<Film> film, list<shared_ptr<Screen> > screens, boost::posix_time::ptime from, boost::posix_time::ptime to)
+{
+       list<libdcp::KDM> kdms = film->make_kdms (screens, from, to);
+          
+       list<ScreenKDM> screen_kdms;
+       
+       list<shared_ptr<Screen> >::iterator i = screens.begin ();
+       list<libdcp::KDM>::iterator j = kdms.begin ();
+       while (i != screens.end() && j != kdms.end ()) {
+               screen_kdms.push_back (ScreenKDM (*i, *j));
+               ++i;
+               ++j;
+       }
+
+       return screen_kdms;
+}
+
+static list<CinemaKDMs>
+make_cinema_kdms (shared_ptr<Film> film, list<shared_ptr<Screen> > screens, boost::posix_time::ptime from, boost::posix_time::ptime to)
+{
+       list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, from, to);
+       list<CinemaKDMs> cinema_kdms;
+
+       while (!screen_kdms.empty ()) {
+               
+               /* Get all the screens from a single cinema */
+
+               CinemaKDMs ck;
+               
+               list<ScreenKDM>::iterator i = screen_kdms.begin ();
+               ck.cinema = i->screen->cinema;
+               ck.screen_kdms.push_back (*i);
+               list<ScreenKDM>::iterator j = i;
+               ++i;
+               screen_kdms.remove (*j);
+               
+               while (i != screen_kdms.end ()) {
+                       if (i->screen->cinema == ck.cinema) {
+                               ck.screen_kdms.push_back (*i);
+                               list<ScreenKDM>::iterator j = i;
+                               ++i;
+                               screen_kdms.remove (*j);
+                       } else {
+                               ++i;
+                       }
+               }
+
+               cinema_kdms.push_back (ck);
+       }
+
+       return cinema_kdms;
+}
+
+void
+write_kdm_files (
+       shared_ptr<Film> film, list<shared_ptr<Screen> > screens, boost::posix_time::ptime from, boost::posix_time::ptime to, boost::filesystem::path directory
+       )
+{
+       list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, from, to);
+
+       /* Write KDMs to the specified directory */
+       for (list<ScreenKDM>::iterator i = screen_kdms.begin(); i != screen_kdms.end(); ++i) {
+               boost::filesystem::path out = directory;
+               out /= tidy_for_filename (i->screen->cinema->name) + "_" + tidy_for_filename (i->screen->name) + ".kdm.xml";
+               i->kdm.as_xml (out);
+       }
+}
+
+void
+write_kdm_zip_files (
+       shared_ptr<Film> film, list<shared_ptr<Screen> > screens, boost::posix_time::ptime from, boost::posix_time::ptime to, boost::filesystem::path directory
+       )
+{
+       list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, from, to);
+
+       for (list<CinemaKDMs>::const_iterator i = cinema_kdms.begin(); i != cinema_kdms.end(); ++i) {
+               boost::filesystem::path path = directory;
+               path /= tidy_for_filename (i->cinema->name) + ".zip";
+               i->make_zip_file (path);
+       }
+}
+
+void
+email_kdms (shared_ptr<Film> film, list<shared_ptr<Screen> > screens, boost::posix_time::ptime from, boost::posix_time::ptime to)
+{
+       list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, from, to);
+
+       for (list<CinemaKDMs>::const_iterator i = cinema_kdms.begin(); i != cinema_kdms.end(); ++i) {
+               
+               boost::filesystem::path zip_file = boost::filesystem::temp_directory_path ();
+               zip_file /= boost::filesystem::unique_path().string() + ".zip";
+               i->make_zip_file (zip_file);
+               
+               /* Send email */
+               
+               quickmail_initialize ();
+               quickmail mail = quickmail_create (Config::instance()->kdm_from().c_str(), "KDM delivery");
+               quickmail_add_to (mail, i->cinema->email.c_str ());
+               
+               string body = Config::instance()->kdm_email().c_str();
+               boost::algorithm::replace_all (body, "$DCP_NAME", film->dcp_name ());
+               
+               quickmail_set_body (mail, body.c_str());
+               quickmail_add_attachment_file (mail, zip_file.string().c_str());
+               char const* error = quickmail_send (mail, Config::instance()->mail_server().c_str(), 25, "", "");
+               if (error) {
+                       quickmail_destroy (mail);
+                       throw StringError (String::compose ("Failed to send KDM email (%1)", error));
+               }
+               quickmail_destroy (mail);
+       }
+}
diff --git a/src/lib/kdm.h b/src/lib/kdm.h
new file mode 100644 (file)
index 0000000..f79656b
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/date_time.hpp>
+#include <boost/filesystem.hpp>
+
+class Screen;
+class Film;
+
+extern void write_kdm_files (
+       boost::shared_ptr<Film> film,
+       std::list<boost::shared_ptr<Screen> > screens,
+       boost::posix_time::ptime from,
+       boost::posix_time::ptime to,
+       boost::filesystem::path directory
+       );
+
+extern void write_kdm_zip_files (
+       boost::shared_ptr<Film> film,
+       std::list<boost::shared_ptr<Screen> > screens,
+       boost::posix_time::ptime from,
+       boost::posix_time::ptime to,
+       boost::filesystem::path directory
+       );
+
+extern void email_kdms (
+       boost::shared_ptr<Film> film,
+       std::list<boost::shared_ptr<Screen> > screens,
+       boost::posix_time::ptime from,
+       boost::posix_time::ptime to
+       );
+
index 8c02ff1581c57ad8689f2e9b50dfa2e36f554d07..e27cf3cc7c06ae045a7b9fa024177224ea679f6e 100644 (file)
@@ -32,6 +32,7 @@ sources = """
           image.cc
           job.cc
           job_manager.cc
+          kdm.cc
           log.cc
           moving_image_content.cc
           moving_image_decoder.cc
@@ -74,7 +75,7 @@ def build(bld):
                  AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE 
                  BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 
                  SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
-                 CURL
+                 CURL ZIP QUICKMAIL
                  """
 
     obj.source = sources + ' version.cc'
index 7646d5ebedeb14587fe42998cd6b92d51d6c0db3..874f19d2a1d7090c35962c0ce5d3167669877349 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -29,8 +29,6 @@
 #include <wx/generic/aboutdlgg.h>
 #include <wx/stdpaths.h>
 #include <wx/cmdline.h>
-#include <quickmail.h>
-#include <zip.h>
 #include "wx/film_viewer.h"
 #include "wx/film_editor.h"
 #include "wx/job_manager_view.h"
@@ -52,6 +50,7 @@
 #include "lib/transcode_job.h"
 #include "lib/exceptions.h"
 #include "lib/cinema.h"
+#include "lib/kdm.h"
 
 using std::cout;
 using std::string;
@@ -226,25 +225,6 @@ setup_menu (wxMenuBar* m)
        m->Append (help, _("&Help"));
 }
 
-struct ScreenKDM
-{
-       ScreenKDM (shared_ptr<Screen> s, libdcp::KDM k)
-               : screen (s)
-               , kdm (k)
-       {}
-       
-       shared_ptr<Screen> screen;
-       libdcp::KDM kdm;
-};
-
-/* Not complete but sufficient for our purposes (we're using
-   ScreenKDM in a list where all the screens will be unique).
-*/
-bool operator== (ScreenKDM const & a, ScreenKDM const & b)
-{
-       return a.screen == b.screen;
-}
-
 class Frame : public wxFrame
 {
 public:
@@ -446,112 +426,13 @@ private:
                        d->Destroy ();
                        return;
                }
-               
+
                try {
-                       list<shared_ptr<Screen> > screens = d->screens ();
-                       list<libdcp::KDM> kdms = film->make_kdms (
-                               screens,
-                               d->from (),
-                               d->until ()
-                               );
-
-                       list<ScreenKDM> screen_kdms;
-                       
-                       list<shared_ptr<Screen> >::iterator i = screens.begin ();
-                       list<libdcp::KDM>::iterator j = kdms.begin ();
-                       while (i != screens.end() && j != kdms.end ()) {
-                               screen_kdms.push_back (ScreenKDM (*i, *j));
-                               ++i;
-                               ++j;
-                       }
-                       
                        if (d->write_to ()) {
-                               /* Write KDMs to the specified directory */
-                               for (list<ScreenKDM>::iterator i = screen_kdms.begin(); i != screen_kdms.end(); ++i) {
-                                       boost::filesystem::path out = d->directory ();
-                                       out /= tidy_for_filename (i->screen->cinema->name) + "_" + tidy_for_filename (i->screen->name) + ".kdm.xml";
-                                       i->kdm.as_xml (out);
-                               }
+                               write_kdm_files (film, d->screens (), d->from (), d->until (), d->directory ());
                        } else {
-                               while (!screen_kdms.empty ()) {
-
-                                       /* Get all the screens from a single cinema */
-
-                                       shared_ptr<Cinema> cinema;
-                                       list<ScreenKDM> cinema_screen_kdms;
-
-                                       list<ScreenKDM>::iterator i = screen_kdms.begin ();
-                                       cinema = i->screen->cinema;
-                                       cinema_screen_kdms.push_back (*i);
-                                       list<ScreenKDM>::iterator j = i;
-                                       ++i;
-                                       screen_kdms.remove (*j);
-
-                                       while (i != screen_kdms.end ()) {
-                                               if (i->screen->cinema == cinema) {
-                                                       cinema_screen_kdms.push_back (*i);
-                                                       list<ScreenKDM>::iterator j = i;
-                                                       ++i;
-                                                       screen_kdms.remove (*j);
-                                               } else {
-                                                       ++i;
-                                               }
-                                       }
-
-                                       /* Make a ZIP file of this cinema's KDMs */
-                                       
-                                       boost::filesystem::path zip_file = boost::filesystem::temp_directory_path ();
-                                       zip_file /= boost::filesystem::unique_path().string() + ".zip";
-                                       struct zip* zip = zip_open (zip_file.string().c_str(), ZIP_CREATE | ZIP_EXCL, 0);
-                                       if (!zip) {
-                                               throw FileError ("could not create ZIP file", zip_file);
-                                       }
-
-                                       list<shared_ptr<string> > kdm_strings;
-
-                                       for (list<ScreenKDM>::const_iterator i = cinema_screen_kdms.begin(); i != cinema_screen_kdms.end(); ++i) {
-                                               shared_ptr<string> kdm (new string (i->kdm.as_xml ()));
-                                               kdm_strings.push_back (kdm);
-                                               
-                                               struct zip_source* source = zip_source_buffer (zip, kdm->c_str(), kdm->length(), 0);
-                                               if (!source) {
-                                                       throw StringError ("could not create ZIP source");
-                                               }
-                                               
-                                               string const name = tidy_for_filename (i->screen->cinema->name) + "_" +
-                                                       tidy_for_filename (i->screen->name) + ".kdm.xml";
-                                               
-                                               if (zip_add (zip, name.c_str(), source) == -1) {
-                                                       throw StringError ("failed to add KDM to ZIP archive");
-                                               }
-                                       }
-
-                                       if (zip_close (zip) == -1) {
-                                               throw StringError ("failed to close ZIP archive");
-                                       }
-
-                                       /* Send email */
-
-                                       quickmail_initialize ();
-                                       quickmail mail = quickmail_create (Config::instance()->kdm_from().c_str(), "KDM delivery");
-                                       quickmail_add_to (mail, cinema->email.c_str ());
-
-                                       string body = Config::instance()->kdm_email().c_str();
-                                       boost::algorithm::replace_all (body, "$DCP_NAME", film->dcp_name ());
-                                       
-                                       quickmail_set_body (mail, body.c_str());
-                                       quickmail_add_attachment_file (mail, zip_file.string().c_str());
-                                       char const* error = quickmail_send (mail, Config::instance()->mail_server().c_str(), 25, "", "");
-                                       if (error) {
-                                               quickmail_destroy (mail);
-                                               throw StringError (String::compose ("Failed to send KDM email (%1)", error));
-                                       }
-                                       quickmail_destroy (mail);
-
-                                       film->log()->log (String::compose ("Send KDM email to %1", cinema->email));
-                               }
+                               email_kdms (film, d->screens (), d->from (), d->until ());
                        }
-                       
                } catch (KDMError& e) {
                        error_dialog (this, e.what ());
                }
index 2942486970c28e5a8c0dc62874ab36aba055abed..fb22434642f7cb51ee28d189ffb1b34df279c5a3 100644 (file)
 #include <getopt.h>
 #include <libdcp/certificates.h>
 #include "lib/film.h"
+#include "lib/cinema.h"
+#include "lib/kdm.h"
+#include "lib/config.h"
+#include "lib/exceptions.h"
 
 using std::string;
+using std::cout;
 using std::cerr;
+using std::list;
 using boost::shared_ptr;
 
 static void
 help (string n)
 {
-       cerr << "Syntax: " << n << " [OPTION] <FILM>\n"
+       cerr << "Syntax: " << n << " [OPTION] [<FILM>]\n"
             << "  -h, --help        show this help\n"
-            << "  -o, --output      output filename\n"
+            << "  -o, --output      output file or directory\n"
             << "  -f, --valid-from  valid from time (e.g. \"2013-09-28 01:41:51\")\n"
             << "  -t, --valid-to    valid to time (e.g. \"2014-09-28 01:41:51\")\n"
-            << "  -c, --certificate file containing projector certificate\n";
+            << "  -c, --certificate file containing projector certificate\n"
+            << "  -z, --zip         ZIP each cinema's KDMs into its own file\n"
+            << "      --cinema      specify a cinema, either by name or email address\n"
+            << "      --cinemas     list known cinemas from the DCP-o-matic settings\n";
 }
 
 int main (int argc, char* argv[])
 {
-       string output;
-       string valid_from;
-       string valid_to;
+       boost::filesystem::path output;
+       boost::posix_time::ptime valid_from;
+       boost::posix_time::ptime valid_to;
        string certificate_file;
+       bool zip = false;
+       string cinema_name;
+       bool cinemas = false;
        
        int option_index = 0;
        while (1) {
@@ -51,10 +63,13 @@ int main (int argc, char* argv[])
                        { "valid-from", required_argument, 0, 'f'},
                        { "valid-to", required_argument, 0, 't'},
                        { "certificate", required_argument, 0, 'c' },
+                       { "cinema", required_argument, 0, 'A' },
+                       { "cinemas", no_argument, 0, 'B' },
+                       { "zip", no_argument, 0, 'z' },
                        { 0, 0, 0, 0 }
                };
 
-               int c = getopt_long (argc, argv, "ho:f:t:c:", long_options, &option_index);
+               int c = getopt_long (argc, argv, "ho:f:t:c:A:Bz", long_options, &option_index);
 
                if (c == -1) {
                        break;
@@ -68,15 +83,32 @@ int main (int argc, char* argv[])
                        output = optarg;
                        break;
                case 'f':
-                       valid_from = optarg;
+                       valid_from = boost::posix_time::time_from_string (optarg);
                        break;
                case 't':
-                       valid_to = optarg;
+                       valid_to = boost::posix_time::time_from_string (optarg);
                        break;
                case 'c':
                        certificate_file = optarg;
                        break;
+               case 'A':
+                       cinema_name = optarg;
+                       break;
+               case 'B':
+                       cinemas = true;
+                       break;
+               case 'z':
+                       zip = true;
+                       break;
+               }
+       }
+
+       if (cinemas) {
+               list<boost::shared_ptr<Cinema> > cinemas = Config::instance()->cinemas ();
+               for (list<boost::shared_ptr<Cinema> >::const_iterator i = cinemas.begin(); i != cinemas.end(); ++i) {
+                       cout << (*i)->name << " (" << (*i)->email << ")\n";
                }
+               exit (EXIT_SUCCESS);
        }
 
        if (optind >= argc) {
@@ -84,6 +116,11 @@ int main (int argc, char* argv[])
                exit (EXIT_FAILURE);
        }
 
+       if (cinema_name.empty() && certificate_file.empty()) {
+               cerr << argv[0] << ": you must specify either a cinema, a screen or a certificate file\n";
+               exit (EXIT_FAILURE);
+       }
+
        string const film_dir = argv[optind];
                        
        dcpomatic_setup ();
@@ -97,12 +134,34 @@ int main (int argc, char* argv[])
                exit (EXIT_FAILURE);
        }
 
-       cerr << "reading " << certificate_file << "\n";
-       shared_ptr<libdcp::Certificate> certificate (new libdcp::Certificate (boost::filesystem::path (certificate_file)));
-       libdcp::KDM kdm = film->make_kdm (
-               certificate, boost::posix_time::time_from_string (valid_from), boost::posix_time::time_from_string (valid_to)
-               );
+       if (cinema_name.empty ()) {
+               shared_ptr<libdcp::Certificate> certificate (new libdcp::Certificate (boost::filesystem::path (certificate_file)));
+               libdcp::KDM kdm = film->make_kdm (certificate, valid_from, valid_to);
+               kdm.as_xml (output);
+       } else {
+
+               list<shared_ptr<Cinema> > cinemas = Config::instance()->cinemas ();
+               list<shared_ptr<Cinema> >::const_iterator i = cinemas.begin();
+               while (i != cinemas.end() && (*i)->name != cinema_name && (*i)->email != cinema_name) {
+                       ++i;
+               }
+
+               if (i == cinemas.end ()) {
+                       cerr << argv[0] << ": could not find cinema \"" << cinema_name << "\"\n";
+                       exit (EXIT_FAILURE);
+               }
+
+               try {
+                       if (zip) {
+                               write_kdm_zip_files (film, (*i)->screens(), valid_from, valid_to, output);
+                       } else {
+                               write_kdm_files (film, (*i)->screens(), valid_from, valid_to, output);
+                       }
+               } catch (FileError& e) {
+                       cerr << argv[0] << ": " << e.what() << " (" << e.file().string() << ")\n";
+                       exit (EXIT_FAILURE);
+               }
+       }
 
-       kdm.as_xml (output);
        return 0;
 }
index b68926830e6faf2d8a330f9a6db1d67df5e4ecee..957547d39857ccc1f32b96cc11724557c39c2a7f 100644 (file)
@@ -15,7 +15,7 @@ def build(bld):
     if not bld.env.DISABLE_GUI:
         for t in ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server']:
             obj = bld(features = 'cxx cxxprogram')
-            obj.uselib = 'DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS ZIP QUICKMAIL'
+            obj.uselib = 'DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS'
             if bld.env.STATIC:
                 obj.uselib += ' GTK'
             obj.includes = ['..']