initial implementation of "bring all media into session folder". Incomplete but basic...
authorPaul Davis <paul@linuxaudiosystems.com>
Tue, 8 Jul 2014 04:53:06 +0000 (00:53 -0400)
committerPaul Davis <paul@linuxaudiosystems.com>
Tue, 8 Jul 2014 04:53:13 +0000 (00:53 -0400)
13 files changed:
gtk2_ardour/ardour.menus.in
gtk2_ardour/editor.h
gtk2_ardour/editor_actions.cc
gtk2_ardour/editor_ops.cc
libs/ardour/ardour/file_source.h
libs/ardour/ardour/session.h
libs/ardour/ardour/sndfilesource.h
libs/ardour/audiofilesource.cc
libs/ardour/file_source.cc
libs/ardour/session.cc
libs/ardour/session_state.cc
libs/ardour/smf_source.cc
libs/ardour/sndfilesource.cc

index 98e21741644525ec8283ba726e94d598acccdb28..1b32529c3a38e49b605327e09c61c065ee0516d8 100644 (file)
       <menuitem action='CloseVideo'/>
 
       <menu name='Export' action='Export'>
-        <menuitem action='ExportAudio'/>
+       <menuitem action='ExportAudio'/>
         <menuitem action='StemExport'/>
         <menuitem action='ExportVideo'/>
       </menu>
+      <menuitem action='bring-into-session'/>
       <menu name='Cleanup' action='Cleanup'>
         <menuitem action='CleanupUnused'/>
         <menuitem action='FlushWastebasket'/>
index 5499de97719de970534a32d9bacc9b1ccf7a4d67..44092069f0049e808bd7928f2f32a0bd54d66c16 100644 (file)
@@ -2099,6 +2099,10 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
        TimeAxisView* _stepping_axis_view;
        void zoom_vertical_modifier_released();
 
+       void bring_in_callback (Gtk::Label*, uint32_t n, uint32_t total, std::string name);
+       void update_bring_in_message (Gtk::Label* label, uint32_t n, uint32_t total, std::string name);
+       void bring_all_sources_into_session ();
+
        friend class Drag;
        friend class RegionDrag;
        friend class RegionMoveDrag;
index 6fea7a1770d66929c66f55d8416e612bf4c26ad5..31f368684bf052781b40a3e0533ddbbf875ec1fd 100644 (file)
@@ -701,6 +701,10 @@ Editor::register_actions ()
        act = ActionManager::register_action (editor_actions, X_("importFromSession"), _("Import From Session"), sigc::mem_fun(*this, &Editor::session_import_dialog));
        ActionManager::write_sensitive_actions.push_back (act);
 
+
+       act = ActionManager::register_action (editor_actions, X_("bring-into-session"), _("Bring all media into session folder"), sigc::mem_fun(*this, &Editor::bring_all_sources_into_session));
+       ActionManager::write_sensitive_actions.push_back (act);
+
        ActionManager::register_toggle_action (editor_actions, X_("ToggleSummary"), _("Show Summary"), sigc::mem_fun (*this, &Editor::set_summary));
 
        ActionManager::register_toggle_action (editor_actions, X_("ToggleGroupTabs"), _("Show Group Tabs"), sigc::mem_fun (*this, &Editor::set_group_tabs));
index 9774efb8539f82a6715a6dccd3805528b633c2de..97d3780c969ee1d1b43f058b8937fc269fc45dc1 100644 (file)
@@ -7045,3 +7045,39 @@ Editor::unlock ()
                start_lock_event_timing ();
        }
 }
+
+void
+Editor::bring_in_callback (Gtk::Label* label, uint32_t n, uint32_t total, string name)
+{
+       Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&Editor::update_bring_in_message, this, label, n, total, name));
+}
+
+void
+Editor::update_bring_in_message (Gtk::Label* label, uint32_t n, uint32_t total, string name)
+{
+       label->set_text (string_compose ("Copying %1, %2 of %3", name, n, total));
+       Gtkmm2ext::UI::instance()->flush_pending ();
+}
+
+void
+Editor::bring_all_sources_into_session ()
+{
+       if (!_session) {
+               return;
+       }
+
+       Gtk::Label msg;
+       ArdourDialog w (_("Moving embedded files into session folder"));
+       w.get_vbox()->pack_start (msg);
+       w.present ();
+       
+       /* flush all pending GUI events because we're about to start copying
+        * files
+        */
+       
+       Gtkmm2ext::UI::instance()->flush_pending ();
+
+       cerr << " Do it\n";
+
+       _session->bring_all_sources_into_session (boost::bind (&Editor::bring_in_callback, this, &msg, _1, _2, _3));
+}
index 8b8adfeb669813f7bb4a4d82f6d47f021e1e0101..8cbbfed0d96047708f789a25aa2f2056a6769872 100644 (file)
@@ -89,6 +89,8 @@ public:
         */
        int rename (const std::string& name);
 
+       virtual void release_descriptor () {}
+
 protected:
        FileSource (Session& session, DataType type,
                    const std::string& path,
index 430c0d7a8c7a5944ecbe5855d399f241e0579d56..53215af2b6d14b1ce0a315a5e2baff99aa41ab58 100644 (file)
@@ -196,11 +196,16 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop
        std::string peak_path (std::string) const;
 
        std::string peak_path_from_audio_path (std::string) const;
+       bool audio_source_name_is_unique (const std::string& name, uint32_t chan);
+       std::string format_audio_source_name (const std::string& legalized_base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required, uint32_t cnt, bool related_exists);
+       std::string new_audio_source_path_for_embedded (const std::string& existing_path);
        std::string new_audio_source_path (const std::string&, uint32_t nchans, uint32_t chan, bool destructive, bool take_required);
        std::string new_midi_source_path (const std::string&);
         RouteList new_route_from_template (uint32_t how_many, const std::string& template_path, const std::string& name);
        std::vector<std::string> get_paths_for_new_sources (bool allow_replacing, const std::string& import_file_path, uint32_t channels);
 
+       int bring_all_sources_into_session (boost::function<void(uint32_t,uint32_t,std::string)> callback);
+
        void process (pframes_t nframes);
 
        BufferSet& get_silent_buffers (ChanCount count = ChanCount::ZERO);
@@ -863,6 +868,7 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop
 
        std::vector<std::string> source_search_path(DataType) const;
        void ensure_search_path_includes (const std::string& path, DataType type);
+       void remove_dir_from_search_path (const std::string& path, DataType type);
 
        std::list<std::string> unknown_processors () const;
 
index 831f8db5f727f685dbf8711cc838497022e6901e..99fb9f4d09bcf866b50c34af347f145d77f1d0a2 100644 (file)
@@ -75,6 +75,8 @@ class LIBARDOUR_API SndFileSource : public AudioFileSource {
 
        static int get_soundfile_info (const std::string& path, SoundFileInfo& _info, std::string& error_msg);
 
+       void release_descriptor ();
+
   protected:
        void set_path (const std::string& p);
        void set_header_timeline_position ();
index 7d34b9d9a52c5aabb81fa4bbfa7d11c13acbb774..9c1b969190c530d8cb2ec95b066b1b02b0c349a1 100644 (file)
@@ -32,6 +32,7 @@
 
 #include "pbd/convert.h"
 #include "pbd/basename.h"
+#include "pbd/file_utils.h"
 #include "pbd/mountpoint.h"
 #include "pbd/stl_delete.h"
 #include "pbd/strsplit.h"
@@ -413,3 +414,4 @@ AudioFileSource::get_interleave_buffer (framecnt_t size)
 
        return ssb->buf;
 }
+       
index 8c41f981b9b480ac5da359096659f294ec313298..bb6d3562fa5d8786d2725adca97a8f4c40f1b301 100644 (file)
@@ -546,6 +546,12 @@ void
 FileSource::set_path (const std::string& newpath)
 {
         _path = newpath;
+       set_within_session_from_path (newpath);
+       if (_within_session) {
+               _origin = Glib::path_get_basename (newpath);
+       } else {
+               _origin = newpath;
+       }
 }
 
 void
@@ -597,3 +603,5 @@ FileSource::rename (const string& newpath)
 
        return 0;
 }
+
+       
index 989b91606554adf7138538d71181733cbbc2f6fa..19a081e17ba6613daf6c26015aed5bdd2d0d2432 100644 (file)
@@ -44,6 +44,7 @@
 #include "pbd/stacktrace.h"
 #include "pbd/file_utils.h"
 #include "pbd/convert.h"
+#include "pbd/md5.h"
 #include "pbd/unwind.h"
 #include "pbd/search_path.h"
 
@@ -3442,94 +3443,168 @@ Session::peak_path (string base) const
        return Glib::build_filename (_session_dir->peak_path(), base + peakfile_suffix);
 }
 
-/** Return a unique name based on \a base for a new internal audio source */
 string
-Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required)
+Session::new_audio_source_path_for_embedded (const std::string& path)
 {
-       uint32_t cnt;
-       string possible_name;
-       const uint32_t limit = 9999; // arbitrary limit on number of files with the same basic name
-       string legalized;
-       string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
-       bool some_related_source_name_exists = false;
+       /* embedded source: 
+        *
+        * we know that the filename is already unique because it exists
+        * out in the filesystem. 
+        *
+        * However, when we bring it into the session, we could get a
+        * collision.
+        *
+        * Eg. two embedded files:
+        * 
+        *          /foo/bar/baz.wav
+        *          /frob/nic/baz.wav
+        *
+        * When merged into session, these collide. 
+        *
+        * There will not be a conflict with in-memory sources
+        * because when the source was created we already picked
+        * a unique name for it.
+        *
+        * This collision is not likely to be common, but we have to guard
+        * against it.  So, if there is a collision, take the md5 hash of the
+        * the path, and use that as the filename instead.
+        */
 
-       possible_name[0] = '\0';
-       legalized = legalize_for_path (base);
+       SessionDirectory sdir (get_best_session_directory_for_new_audio());
+       string base = Glib::path_get_basename (path);
+       string newpath = Glib::build_filename (sdir.sound_path(), base);
+       
+       if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
 
-       std::vector<string> sdirs = source_search_path(DataType::AUDIO);
+               MD5 md5;
 
-       // Find a "version" of the base name that doesn't exist in any of the possible directories.
+               md5.digestString (path.c_str());
+               md5.writeToString ();
+               base = md5.digestChars;
 
-       for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) {
+               /* XXX base needs suffix from path */
+               
+               newpath = Glib::build_filename (sdir.sound_path(), base);
 
-               vector<space_and_path>::iterator i;
-               uint32_t existing = 0;
+               /* if this collides, we're screwed */
 
-               for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) {
+               if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
+                       error << string_compose (_("Merging embedded file %1: name collision AND md5 hash collision!"), path) << endmsg;
+                       return string();
+               }
 
-                       ostringstream sstr;
+       }
 
-                       if (destructive) {
-                               sstr << 'T';
-                               sstr << setfill ('0') << setw (4) << cnt;
-                               sstr << legalized;
-                       } else {
-                               sstr << legalized;
-                               
-                               if (take_required || some_related_source_name_exists) {
-                                       sstr << '-';
-                                       sstr << cnt;
-                               }
-                       }
-                       
-                       if (nchan == 2) {
-                               if (chan == 0) {
-                                       sstr << "%L";
-                               } else {
-                                       sstr << "%R";
-                               }
-                       } else if (nchan > 2 && nchan < 26) {
-                               sstr << '%';
-                               sstr << 'a' + chan;
-                       } 
+       return newpath;
+}
 
-                       sstr << ext;
+bool
+Session::audio_source_name_is_unique (const string& name, uint32_t chan)
+{
+       std::vector<string> sdirs = source_search_path (DataType::AUDIO);
+       vector<space_and_path>::iterator i;
+       uint32_t existing = 0;
+       string basename = PBD::basename_nosuffix (name);
 
-                       possible_name = sstr.str();
-                       const string spath = (*i);
+       for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) {
+               
+               /* note that we search *without* the extension so that
+                  we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf"
+                  in the event that this new name is required for
+                  a file format change.
+               */
 
-                       /* note that we search *without* the extension so that
-                          we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf"
-                          in the event that this new name is required for
-                          a file format change.
-                       */
+               const string spath = *i;
+               
+               if (matching_unsuffixed_filename_exists_in (spath, basename)) {
+                       existing++;
+                       break;
+               }
+               
+               /* it is possible that we have the path already
+                * assigned to a source that has not yet been written
+                * (ie. the write source for a diskstream). we have to
+                * check this in order to make sure that our candidate
+                * path isn't used again, because that can lead to
+                * two Sources point to the same file with different
+                * notions of their removability.
+                */
+               
+               
+               string possible_path = Glib::build_filename (spath, name);
 
-                       if (matching_unsuffixed_filename_exists_in (spath, possible_name)) {
-                               existing++;
-                               break;
-                       }
+               if (audio_source_by_path_and_channel (possible_path, chan)) {
+                       existing++;
+                       break;
+               }
+       }
+       
+       return (existing == 0);
+}
 
-                       /* it is possible that we have the path already
-                        * assigned to a source that has not yet been written
-                        * (ie. the write source for a diskstream). we have to
-                        * check this in order to make sure that our candidate
-                        * path isn't used again, because that can lead to
-                        * two Sources point to the same file with different
-                        * notions of their removability.
-                        */
+string
+Session::format_audio_source_name (const string& legalized_base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required, uint32_t cnt, bool related_exists)
+{
+       ostringstream sstr;
+       const string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
+       
+       if (destructive) {
+               sstr << 'T';
+               sstr << setfill ('0') << setw (4) << cnt;
+               sstr << legalized_base;
+       } else {
+               sstr << legalized_base;
+               
+               if (take_required || related_exists) {
+                       sstr << '-';
+                       sstr << cnt;
+               }
+       }
+       
+       if (nchan == 2) {
+               if (chan == 0) {
+                       sstr << "%L";
+               } else {
+                       sstr << "%R";
+               }
+       } else if (nchan > 2) {
+               if (nchan < 26) {
+                       sstr << '%';
+                       sstr << 'a' + chan;
+               } else {
+                       /* XXX what? more than 26 channels! */
+                       sstr << '%';
+                       sstr << chan+1;
+               }
+       }
+       
+       sstr << ext;
 
-                       string possible_path = Glib::build_filename (spath, possible_name);
+       return sstr.str();
+}
 
-                       if (audio_source_by_path_and_channel (possible_path, chan)) {
-                               existing++;
-                               break;
-                       }
-               }
+/** Return a unique name based on \a base for a new internal audio source */
+string
+Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required)
+{
+       uint32_t cnt;
+       string possible_name;
+       const uint32_t limit = 9999; // arbitrary limit on number of files with the same basic name
+       string legalized;
+       bool some_related_source_name_exists = false;
 
-               if (existing == 0) {
+       legalized = legalize_for_path (base);
+
+       // Find a "version" of the base name that doesn't exist in any of the possible directories.
+
+       for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) {
+
+               possible_name = format_audio_source_name (legalized, nchan, chan, destructive, take_required, cnt, some_related_source_name_exists);
+               
+               if (audio_source_name_is_unique (possible_name, chan)) {
                        break;
                }
-
+               
                some_related_source_name_exists = true;
 
                if (cnt > limit) {
@@ -4776,6 +4851,33 @@ Session::ensure_search_path_includes (const string& path, DataType type)
        }
 }
 
+void
+Session::remove_dir_from_search_path (const string& dir, DataType type)
+{
+       Searchpath sp;
+
+       switch (type) {
+       case DataType::AUDIO:
+               sp = Searchpath(config.get_audio_search_path ());
+               break;
+       case DataType::MIDI:
+               sp = Searchpath (config.get_midi_search_path ());
+               break;
+       }
+
+       sp -= dir;
+
+       switch (type) {
+       case DataType::AUDIO:
+               config.set_audio_search_path (sp.to_string());
+               break;
+       case DataType::MIDI:
+               config.set_midi_search_path (sp.to_string());
+               break;
+       }
+
+}
+
 boost::shared_ptr<Speakers>
 Session::get_speakers()
 {
index 9321973f7e94009728136456ed759cf43b587ebe..21088227d50f7010eb0b4be486207e002f7ddbd8 100644 (file)
@@ -3773,3 +3773,92 @@ Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFo
 
        return !(found_sr && found_data_format); // zero if they are both found
 }
+
+typedef std::vector<boost::shared_ptr<FileSource> > SeveralFileSources;
+typedef std::map<std::string,SeveralFileSources> SourcePathMap;
+
+int
+Session::bring_all_sources_into_session (boost::function<void(uint32_t,uint32_t,string)> callback)
+{
+       uint32_t total = 0;
+       uint32_t n = 0;
+       SourcePathMap source_path_map;
+       string new_path;
+       boost::shared_ptr<AudioFileSource> afs;
+       int ret = 0;
+
+       {
+
+               Glib::Threads::Mutex::Lock lm (source_lock);
+               
+               cerr << " total sources = " << sources.size();
+               
+               for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+                       boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
+                       
+                       if (!fs) {
+                               continue;
+                       }
+                       
+                       if (fs->within_session()) {
+                               cerr << "skip " << fs->name() << endl;
+                               continue;
+                       }
+                       
+                       if (source_path_map.find (fs->path()) != source_path_map.end()) {
+                               source_path_map[fs->path()].push_back (fs);
+                       } else {
+                               SeveralFileSources v;
+                               v.push_back (fs);
+                               source_path_map.insert (make_pair (fs->path(), v));
+                       }
+                       
+                       total++;
+               }
+               
+               cerr << " fsources = " << total << endl;
+               
+               for (SourcePathMap::iterator i = source_path_map.begin(); i != source_path_map.end(); ++i) {
+                       
+                       /* tell caller where we are */
+                       
+                       string old_path = i->first;
+                       
+                       callback (n, total, old_path);
+                       
+                       cerr << old_path << endl;
+                       
+                       new_path.clear ();
+                       
+                       switch (i->second.front()->type()) {
+                       case DataType::AUDIO:
+                               new_path = new_audio_source_path_for_embedded (old_path);
+                               break;
+                               
+                       case DataType::MIDI:
+                               break;
+                       }
+                       
+                       cerr << "Move " << old_path << " => " << new_path << endl;
+                       
+                       if (!copy_file (old_path, new_path)) {
+                               cerr << "failed !\n";
+                               ret = -1;
+                       }
+                       
+                       /* make sure we stop looking in the external
+                          dir/folder. Remember, this is an all-or-nothing
+                          operations, it doesn't merge just some files.
+                       */
+                       remove_dir_from_search_path (Glib::path_get_dirname (old_path), i->second.front()->type());
+
+                       for (SeveralFileSources::iterator f = i->second.begin(); f != i->second.end(); ++f) {
+                               (*f)->set_path (new_path);
+                       }
+               }
+       }
+
+       save_state ("", false, false);
+
+       return ret;
+}
index e39ef3f548408aa36e2681279710b6f2f8cbafbd..6ec7c5a78ce8318afc8890ea14df380769b91bbf 100644 (file)
@@ -26,6 +26,7 @@
 #include <errno.h>
 #include <regex.h>
 
+#include "pbd/file_utils.h"
 #include "pbd/stl_delete.h"
 #include "pbd/strsplit.h"
 
@@ -717,4 +718,5 @@ SMFSource::prevent_deletion ()
   
        _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
 }
-
+               
+       
index af25b3e76f84ca9cee2a1840d1ec1e705a444beb..58fbab233b3f05bfa1d355a2817caa90e15529f5 100644 (file)
@@ -1016,3 +1016,12 @@ SndFileSource::set_path (const string& p)
                 _descriptor->set_path (_path);
         }
 }
+
+void
+SndFileSource::release_descriptor ()
+{
+       if (_descriptor) {
+               _descriptor->release ();
+               _descriptor = 0;
+       }
+}