Replace use of PBD::sys::path in ardour/session_state_utils.h
[ardour.git] / libs / ardour / session_state.cc
index 5d9967cc98dd5d01f79645296da707861e15b19d..3656e0f07eac41370c5789b407f4267f916c29cc 100644 (file)
@@ -28,8 +28,6 @@
 #include <fstream>
 #include <string>
 #include <cerrno>
-
-
 #include <cstdio> /* snprintf(3) ... grrr */
 #include <cmath>
 #include <unistd.h>
 #include <sys/mount.h>
 #endif
 
+#ifdef HAVE_SYS_STATVFS_H
+#include <sys/statvfs.h>
+#endif
+
+#include <glib.h>
+
 #include <glibmm.h>
 #include <glibmm/thread.h>
 
+#include <boost/algorithm/string.hpp>
+
 #include "midi++/mmc.h"
 #include "midi++/port.h"
 #include "midi++/manager.h"
 
+#include "evoral/SMF.hpp"
+
 #include "pbd/boost_debug.h"
 #include "pbd/basename.h"
 #include "pbd/controllable_descriptor.h"
 
 #include "ardour/amp.h"
 #include "ardour/audio_diskstream.h"
-#include "ardour/audio_playlist_source.h"
 #include "ardour/audio_track.h"
 #include "ardour/audioengine.h"
 #include "ardour/audiofilesource.h"
-#include "ardour/audioplaylist.h"
 #include "ardour/audioregion.h"
-#include "ardour/auditioner.h"
 #include "ardour/automation_control.h"
-#include "ardour/buffer.h"
 #include "ardour/butler.h"
-#include "ardour/configuration.h"
 #include "ardour/control_protocol_manager.h"
-#include "ardour/crossfade.h"
-#include "ardour/cycle_timer.h"
 #include "ardour/directory_names.h"
 #include "ardour/filename_extensions.h"
-#include "ardour/io_processor.h"
 #include "ardour/location.h"
-#include "ardour/midi_diskstream.h"
+#include "ardour/midi_model.h"
 #include "ardour/midi_patch_manager.h"
-#include "ardour/midi_playlist.h"
 #include "ardour/midi_region.h"
 #include "ardour/midi_source.h"
 #include "ardour/midi_track.h"
 #include "ardour/named_selection.h"
 #include "ardour/pannable.h"
-#include "ardour/processor.h"
+#include "ardour/playlist_factory.h"
 #include "ardour/port.h"
+#include "ardour/processor.h"
 #include "ardour/proxy_controllable.h"
+#include "ardour/recent_sessions.h"
 #include "ardour/region_factory.h"
 #include "ardour/route_group.h"
 #include "ardour/send.h"
 #include "ardour/session.h"
 #include "ardour/session_directory.h"
 #include "ardour/session_metadata.h"
-#include "ardour/session_state_utils.h"
 #include "ardour/session_playlists.h"
+#include "ardour/session_state_utils.h"
 #include "ardour/session_utils.h"
 #include "ardour/silentfilesource.h"
-#include "ardour/slave.h"
-#include "ardour/smf_source.h"
-#include "ardour/sndfile_helpers.h"
 #include "ardour/sndfilesource.h"
 #include "ardour/source_factory.h"
+#include "ardour/speakers.h"
 #include "ardour/template_utils.h"
 #include "ardour/tempo.h"
 #include "ardour/ticker.h"
 #include "ardour/user_bundle.h"
-#include "ardour/utils.h"
-#include "ardour/utils.h"
-#include "ardour/version.h"
-#include "ardour/playlist_factory.h"
 
 #include "control_protocol/control_protocol.h"
 
@@ -131,7 +126,7 @@ using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
 
-
+/** @param snapshot_name Snapshot name, without the .ardour prefix */
 void
 Session::first_stage_init (string fullpath, string snapshot_name)
 {
@@ -174,6 +169,7 @@ Session::first_stage_init (string fullpath, string snapshot_name)
        _solo_isolated_cnt = 0;
        g_atomic_int_set (&processing_prohibited, 0);
        _transport_speed = 0;
+       _default_transport_speed = 1.0;
        _last_transport_speed = 0;
        _target_transport_speed = 0;
        auto_play_legal = false;
@@ -219,12 +215,14 @@ Session::first_stage_init (string fullpath, string snapshot_name)
         _step_editors = 0;
         no_questions_about_missing_files = false;
         _speakers.reset (new Speakers);
+       _clicks_cleared = 0;
+       ignore_route_processor_changes = false;
+       _pre_export_mmc_enabled = false;
 
        AudioDiskstream::allocate_working_buffers();
 
        /* default short fade = 15ms */
 
-       Crossfade::set_short_xfade_length ((framecnt_t) floor (config.get_short_xfade_seconds() * frame_rate()));
        SndFileSource::setup_standard_crossfades (*this, frame_rate());
 
        last_mmc_step.tv_sec = 0;
@@ -290,7 +288,7 @@ Session::first_stage_init (string fullpath, string snapshot_name)
 int
 Session::second_stage_init ()
 {
-       AudioFileSource::set_peak_dir (_session_dir->peak_path().to_string());
+       AudioFileSource::set_peak_dir (_session_dir->peak_path());
 
        if (!_is_new) {
                if (load_state (_current_snapshot_name)) {
@@ -341,6 +339,9 @@ Session::second_stage_init ()
        _engine.Halted.connect_same_thread (*this, boost::bind (&Session::engine_halted, this));
        _engine.Xrun.connect_same_thread (*this, boost::bind (&Session::xrun_recovery, this));
 
+       midi_clock = new MidiClockTicker ();
+       midi_clock->set_session (this);
+
        try {
                when_engine_running ();
        }
@@ -364,7 +365,6 @@ Session::second_stage_init ()
        MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset));
        MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (Timecode::Time ()));
 
-       MidiClockTicker::instance().set_session (this);
        MIDI::Name::MidiPatchManager::instance().set_session (this);
 
        /* initial program change will be delivered later; see ::config_changed() */
@@ -392,7 +392,7 @@ Session::raid_path () const
        SearchPath raid_search_path;
 
        for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
-               raid_search_path += sys::path((*i).path);
+               raid_search_path += (*i).path;
        }
 
        return raid_search_path.to_string ();
@@ -415,7 +415,7 @@ Session::setup_raid_path (string path)
        SearchPath midi_search_path;
 
        for (SearchPath::const_iterator i = search_path.begin(); i != search_path.end(); ++i) {
-               sp.path = (*i).to_string ();
+               sp.path = *i;
                sp.blocks = 0; // not needed
                session_dirs.push_back (sp);
 
@@ -433,7 +433,7 @@ bool
 Session::path_is_within_session (const std::string& path)
 {
        for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
-               if (path.find ((*i).path) == 0) {
+               if (PBD::sys::path_is_within (i->path, path)) {
                        return true;
                }
        }
@@ -445,35 +445,35 @@ Session::ensure_subdirs ()
 {
        string dir;
 
-       dir = session_directory().peak_path().to_string();
+       dir = session_directory().peak_path();
 
        if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
                error << string_compose(_("Session: cannot create session peakfile folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
                return -1;
        }
 
-       dir = session_directory().sound_path().to_string();
+       dir = session_directory().sound_path();
 
        if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
                error << string_compose(_("Session: cannot create session sounds dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
                return -1;
        }
 
-       dir = session_directory().midi_path().to_string();
+       dir = session_directory().midi_path();
 
        if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
                error << string_compose(_("Session: cannot create session midi dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
                return -1;
        }
 
-       dir = session_directory().dead_path().to_string();
+       dir = session_directory().dead_path();
 
        if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
                error << string_compose(_("Session: cannot create session dead sounds folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
                return -1;
        }
 
-       dir = session_directory().export_path().to_string();
+       dir = session_directory().export_path();
 
        if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
                error << string_compose(_("Session: cannot create session export folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
@@ -494,12 +494,21 @@ Session::ensure_subdirs ()
                return -1;
        }
 
+       dir = externals_dir ();
+
+       if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
+               error << string_compose(_("Session: cannot create session externals folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
+               return -1;
+       }
+
        return 0;
 }
 
-/** Caller must not hold process lock */
+/** @param session_template directory containing session template, or empty.
+ *  Caller must not hold process lock.
+ */
 int
-Session::create (const string& mix_template, BusProfile* bus_profile)
+Session::create (const string& session_template, BusProfile* bus_profile)
 {
        if (g_mkdir_with_parents (_path.c_str(), 0755) < 0) {
                error << string_compose(_("Session: cannot create session folder \"%1\" (%2)"), _path, strerror (errno)) << endmsg;
@@ -512,8 +521,8 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
 
        _writable = exists_and_writable (sys::path (_path));
 
-       if (!mix_template.empty()) {
-               std::string in_path = mix_template;
+       if (!session_template.empty()) {
+               std::string in_path = session_template_dir_to_file (session_template);
 
                ifstream in(in_path.c_str());
 
@@ -527,26 +536,28 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
                        if (out) {
                                out << in.rdbuf();
                                 _is_new = false;
+
+                               /* Copy plugin state files from template to new session */
+                               sys::path template_plugins = session_template;
+                               template_plugins /= X_("plugins");
+                               sys::copy_files (template_plugins, plugins_dir ());
+                               
                                return 0;
 
                        } else {
-                               error << string_compose (_("Could not open %1 for writing mix template"), out_path)
+                               error << string_compose (_("Could not open %1 for writing session template"), out_path)
                                        << endmsg;
                                return -1;
                        }
 
                } else {
-                       error << string_compose (_("Could not open mix template %1 for reading"), in_path)
+                       error << string_compose (_("Could not open session template %1 for reading"), in_path)
                                << endmsg;
                        return -1;
                }
 
        }
 
-       /* Instantiate metadata */
-
-       _metadata = new SessionMetadata ();
-
        /* set initial start + end point */
 
        _state_of_the_state = Clean;
@@ -556,7 +567,6 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
         if (bus_profile) {
 
                RouteList rl;
-               int control_id = 1;
                 ChanCount count(DataType::AUDIO, bus_profile->master_out_channels);
 
                if (bus_profile->master_out_channels) {
@@ -565,42 +575,23 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
                                 return -1;
                         }
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                       boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
+                       // boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
 #endif
                        {
                                Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
                                r->input()->ensure_io (count, false, this);
                                r->output()->ensure_io (count, false, this);
                        }
-                       r->set_remote_control_id (control_id++);
 
                        rl.push_back (r);
 
-                        if (Config->get_use_monitor_bus()) {
-                               boost::shared_ptr<Route> r (new Route (*this, _("monitor"), Route::MonitorOut, DataType::AUDIO));
-                                if (r->init ()) {
-                                        return -1;
-                                }
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                                boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
-#endif
-                               {
-                                       Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
-                                       r->input()->ensure_io (count, false, this);
-                                       r->output()->ensure_io (count, false, this);
-                               }
-                                r->set_remote_control_id (control_id);
-
-                                rl.push_back (r);
-                        }
-
                } else {
                        /* prohibit auto-connect to master, because there isn't one */
                        bus_profile->output_ac = AutoConnectOption (bus_profile->output_ac & ~AutoConnectMaster);
                }
 
                if (!rl.empty()) {
-                       add_routes (rl, false, false);
+                       add_routes (rl, false, false, false);
                }
 
                 /* this allows the user to override settings with an environment variable.
@@ -615,6 +606,10 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
                 Config->set_output_auto_connect (bus_profile->output_ac);
         }
 
+       if (Config->get_use_monitor_bus() && bus_profile) {
+               add_monitor_section ();
+       }
+
        save_state ("");
 
        return 0;
@@ -661,8 +656,8 @@ Session::rename_state (string old_name, string new_name)
        const string old_xml_filename = legalize_for_path (old_name) + statefile_suffix;
        const string new_xml_filename = legalize_for_path (new_name) + statefile_suffix;
 
-       const sys::path old_xml_path = _session_dir->root_path() / old_xml_filename;
-       const sys::path new_xml_path = _session_dir->root_path() / new_xml_filename;
+       const sys::path old_xml_path(Glib::build_filename (_session_dir->root_path(), old_xml_filename));
+       const sys::path new_xml_path(Glib::build_filename (_session_dir->root_path(), new_xml_filename));
 
        try
        {
@@ -681,14 +676,14 @@ Session::rename_state (string old_name, string new_name)
 void
 Session::remove_state (string snapshot_name)
 {
-       if (snapshot_name == _current_snapshot_name || snapshot_name == _name) {
+       if (!_writable || snapshot_name == _current_snapshot_name || snapshot_name == _name) {
                // refuse to remove the current snapshot or the "main" one
                return;
        }
 
-       sys::path xml_path(_session_dir->root_path());
+       std::string xml_path(_session_dir->root_path());
 
-       xml_path /= legalize_for_path (snapshot_name) + statefile_suffix;
+       xml_path = Glib::build_filename (xml_path, legalize_for_path (snapshot_name) + statefile_suffix);
 
        if (!create_backup_file (xml_path)) {
                // don't remove it if a backup can't be made
@@ -758,7 +753,7 @@ int
 Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot)
 {
        XMLTree tree;
-       sys::path xml_path(_session_dir->root_path());
+       std::string xml_path(_session_dir->root_path());
 
        if (!_writable || (_state_of_the_state & CannotSave)) {
                return 1;
@@ -774,8 +769,12 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
        /* tell sources we're saving first, in case they write out to a new file
         * which should be saved with the state rather than the old one */
        for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
-               i->second->session_saved();
-        }
+               try {
+                       i->second->session_saved();
+               } catch (Evoral::SMF::FileError& e) {
+                       error << string_compose ("Could not write to MIDI file %1; MIDI data not saved.", e.file_name ()) << endmsg;
+               }
+       }
 
        tree.set_root (&get_state());
 
@@ -789,7 +788,7 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
 
                /* proper save: use statefile_suffix (.ardour in English) */
 
-               xml_path /= legalize_for_path (snapshot_name) + statefile_suffix;
+               xml_path = Glib::build_filename (xml_path, legalize_for_path (snapshot_name) + statefile_suffix);
 
                /* make a backup copy of the old file */
 
@@ -801,7 +800,7 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
        } else {
 
                /* pending save: use pending_suffix (.pending in English) */
-               xml_path /= legalize_for_path (snapshot_name) + pending_suffix;
+               xml_path = Glib::build_filename (xml_path, legalize_for_path (snapshot_name) + pending_suffix);
        }
 
        sys::path tmp_path(_session_dir->root_path());
@@ -817,9 +816,9 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
 
        } else {
 
-               if (rename (tmp_path.to_string().c_str(), xml_path.to_string().c_str()) != 0) {
+               if (::rename (tmp_path.to_string().c_str(), xml_path.c_str()) != 0) {
                        error << string_compose (_("could not rename temporary session file %1 to %2"),
-                                       tmp_path.to_string(), xml_path.to_string()) << endmsg;
+                                       tmp_path.to_string(), xml_path) << endmsg;
                        sys::remove (tmp_path);
                        return -1;
                }
@@ -918,39 +917,42 @@ Session::load_state (string snapshot_name)
                /* no version implies very old version of Ardour */
                Stateful::loading_state_version = 1000;
        } else {
-               int major;
-               int minor;
-               int micro;
-
-               sscanf (prop->value().c_str(), "%d.%d.%d", &major, &minor, &micro);
-               Stateful::loading_state_version = (major * 1000) + minor;
+               if (prop->value().find ('.') != string::npos) {
+                       /* old school version format */
+                       if (prop->value()[0] == '2') {
+                               Stateful::loading_state_version = 2000;
+                       } else {
+                               Stateful::loading_state_version = 3000;
+                       }
+               } else {
+                       Stateful::loading_state_version = atoi (prop->value());
+               }
        }
 
-       if (Stateful::loading_state_version < CURRENT_SESSION_FILE_VERSION) {
+       if (Stateful::loading_state_version < CURRENT_SESSION_FILE_VERSION && _writable) {
 
                sys::path backup_path(_session_dir->root_path());
 
-               backup_path /= legalize_for_path (snapshot_name) + "-1" + statefile_suffix;
+               backup_path /= string_compose ("%1-%2%3", legalize_for_path (snapshot_name), Stateful::loading_state_version, statefile_suffix);
 
-               // only create a backup once
-               if (sys::exists (backup_path)) {
-                       return 0;
-               }
-
-               info << string_compose (_("Copying old session file %1 to %2\nUse %2 with %3 versions before 2.0 from now on"),
-                                       xmlpath.to_string(), backup_path.to_string(), PROGRAM_NAME)
-                    << endmsg;
+               // only create a backup for a given statefile version once
 
-               try
-               {
-                       sys::copy_file (xmlpath, backup_path);
-               }
-               catch(sys::filesystem_error& ex)
-               {
-                       error << string_compose (_("Unable to make backup of state file %1 (%2)"),
-                                       xmlpath.to_string(), ex.what())
-                               << endmsg;
-                       return -1;
+               if (!sys::exists (backup_path)) {
+                       
+                       info << string_compose (_("Copying old session file %1 to %2\nUse %2 with %3 versions before 2.0 from now on"),
+                                               xmlpath.to_string(), backup_path.to_string(), PROGRAM_NAME)
+                            << endmsg;
+                       
+                       try {
+                               sys::copy_file (xmlpath, backup_path);
+                               
+                       } catch (sys::filesystem_error& ex) {
+                               
+                               error << string_compose (_("Unable to make backup of state file %1 (%2)"),
+                                                        xmlpath.to_string(), ex.what())
+                                     << endmsg;
+                               return -1;
+                       }
                }
        }
 
@@ -985,15 +987,14 @@ Session::get_template()
 }
 
 XMLNode&
-Session::state(bool full_state)
+Session::state (bool full_state)
 {
        XMLNode* node = new XMLNode("Session");
        XMLNode* child;
 
-       // store libardour version, just in case
        char buf[16];
-       snprintf(buf, sizeof(buf), "%d.%d.%d", libardour3_major_version, libardour3_minor_version, libardour3_micro_version);
-       node->add_property("version", string(buf));
+       snprintf(buf, sizeof(buf), "%d", CURRENT_SESSION_FILE_VERSION);
+       node->add_property("version", buf);
 
        /* store configuration settings */
 
@@ -1047,7 +1048,7 @@ Session::state(bool full_state)
 
        node->add_child_nocopy (config.get_variables ());
 
-       node->add_child_nocopy (_metadata->get_state());
+       node->add_child_nocopy (ARDOUR::SessionMetadata::Metadata()->get_state());
 
        child = node->add_child ("Sources");
 
@@ -1162,15 +1163,16 @@ Session::state(bool full_state)
        }
 
        if (_click_io) {
-               child = node->add_child ("Click");
-               child->add_child_nocopy (_click_io->state (full_state));
+               XMLNode* gain_child = node->add_child ("Click");
+               gain_child->add_child_nocopy (_click_io->state (full_state));
+               gain_child->add_child_nocopy (_click_gain->state (full_state));
        }
 
        if (full_state) {
-               child = node->add_child ("NamedSelections");
+               XMLNode* ns_child = node->add_child ("NamedSelections");
                for (NamedSelectionList::iterator i = named_selections.begin(); i != named_selections.end(); ++i) {
                        if (full_state) {
-                               child->add_child_nocopy ((*i)->get_state());
+                               ns_child->add_child_nocopy ((*i)->get_state());
                        }
                }
        }
@@ -1208,10 +1210,6 @@ Session::set_state (const XMLNode& node, int version)
                return -1;
        }
 
-       if ((prop = node.property ("version")) != 0) {
-               version = atoi (prop->value ()) * 1000;
-       }
-
        if ((prop = node.property ("name")) != 0) {
                _name = prop->value ();
        }
@@ -1228,7 +1226,7 @@ Session::set_state (const XMLNode& node, int version)
                }
        }
 
-       setup_raid_path(_session_dir->root_path().to_string());
+       setup_raid_path(_session_dir->root_path());
 
        if ((prop = node.property (X_("id-counter"))) != 0) {
                uint64_t x;
@@ -1263,11 +1261,29 @@ Session::set_state (const XMLNode& node, int version)
        if (version >= 3000) {
                if ((child = find_named_node (node, "Metadata")) == 0) {
                        warning << _("Session: XML state has no metadata section") << endmsg;
-               } else if (_metadata->set_state (*child, version)) {
+               } else if ( ARDOUR::SessionMetadata::Metadata()->set_state (*child, version) ) {
                        goto out;
                }
        }
 
+        if ((child = find_named_node (node, X_("Speakers"))) != 0) {
+                _speakers->set_state (*child, version);
+        }
+
+       if ((child = find_named_node (node, "Sources")) == 0) {
+               error << _("Session: XML state has no sources section") << endmsg;
+               goto out;
+       } else if (load_sources (*child)) {
+               goto out;
+       }
+
+       if ((child = find_named_node (node, "TempoMap")) == 0) {
+               error << _("Session: XML state has no Tempo Map section") << endmsg;
+               goto out;
+       } else if (_tempo_map->set_state (*child, version)) {
+               goto out;
+       }
+
        if ((child = find_named_node (node, "Locations")) == 0) {
                error << _("Session: XML state has no locations section") << endmsg;
                goto out;
@@ -1275,10 +1291,6 @@ Session::set_state (const XMLNode& node, int version)
                goto out;
        }
 
-        if ((child = find_named_node (node, X_("Speakers"))) != 0) {
-                _speakers->set_state (*child, version);
-        }
-
        Location* location;
 
        if ((location = _locations->auto_loop_location()) != 0) {
@@ -1298,20 +1310,6 @@ Session::set_state (const XMLNode& node, int version)
                AudioFileSource::set_header_position_offset (_session_range_location->start());
        }
 
-       if ((child = find_named_node (node, "Sources")) == 0) {
-               error << _("Session: XML state has no sources section") << endmsg;
-               goto out;
-       } else if (load_sources (*child)) {
-               goto out;
-       }
-
-       if ((child = find_named_node (node, "TempoMap")) == 0) {
-               error << _("Session: XML state has no Tempo Map section") << endmsg;
-               goto out;
-       } else if (_tempo_map->set_state (*child, version)) {
-               goto out;
-       }
-
        if ((child = find_named_node (node, "Regions")) == 0) {
                error << _("Session: XML state has no Regions section") << endmsg;
                goto out;
@@ -1404,13 +1402,21 @@ Session::set_state (const XMLNode& node, int version)
        if ((child = find_named_node (node, "Click")) == 0) {
                warning << _("Session: XML state has no click section") << endmsg;
        } else if (_click_io) {
-               _click_io->set_state (*child, version);
+               const XMLNodeList& children (child->children());
+               XMLNodeList::const_iterator i = children.begin();
+               _click_io->set_state (**i, version);
+               ++i;
+               if (i != children.end()) {
+                       _click_gain->set_state (**i, version);
+               }
        }
 
        if ((child = find_named_node (node, "ControlProtocols")) != 0) {
                ControlProtocolManager::instance().set_protocol_states (*child);
        }
 
+       update_have_rec_enabled_track ();
+
        /* here beginneth the second phase ... */
 
        StateReady (); /* EMIT SIGNAL */
@@ -1451,7 +1457,7 @@ Session::load_routes (const XMLNode& node, int version)
                new_routes.push_back (route);
        }
 
-       add_routes (new_routes, false, false);
+       add_routes (new_routes, false, false, false);
 
        return 0;
 }
@@ -1495,7 +1501,7 @@ Session::XMLRouteFactory (const XMLNode& node, int version)
                 }
 
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
+                // boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
 #endif
                 ret = track;
 
@@ -1504,7 +1510,7 @@ Session::XMLRouteFactory (const XMLNode& node, int version)
 
                 if (r->init () == 0 && r->set_state (node, version) == 0) {
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                        boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
+                        // boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
 #endif
                         ret = r;
                 }
@@ -1567,7 +1573,7 @@ Session::XMLRouteFactory_2X (const XMLNode& node, int version)
                track->set_diskstream (*i);
 
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
+                // boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
 #endif
                 ret = track;
 
@@ -1576,7 +1582,7 @@ Session::XMLRouteFactory_2X (const XMLNode& node, int version)
 
                 if (r->init () == 0 && r->set_state (node, version) == 0) {
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                        boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
+                        // boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
 #endif
                         ret = r;
                 }
@@ -1660,11 +1666,26 @@ Session::load_nested_sources (const XMLNode& node)
 
        for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
                if ((*niter)->name() == "Source") {
-                       try {
-                               SourceFactory::create (*this, **niter, true);
+
+                       /* it may already exist, so don't recreate it unnecessarily 
+                        */
+
+                       XMLProperty* prop = (*niter)->property (X_("id"));
+                       if (!prop) {
+                               error << _("Nested source has no ID info in session state file! (ignored)") << endmsg;
+                               continue;
                        }
-                       catch (failed_constructor& err) {
-                               error << string_compose (_("Cannot reconstruct nested source for region %1"), name()) << endmsg;
+
+                       ID source_id (prop->value());
+
+                       if (!source_by_id (source_id)) {
+
+                               try {
+                                       SourceFactory::create (*this, **niter, true);
+                               }
+                               catch (failed_constructor& err) {
+                                       error << string_compose (_("Cannot reconstruct nested source for region %1"), name()) << endmsg;
+                               }
                        }
                }
        }
@@ -2021,88 +2042,90 @@ Session::save_template (string template_name)
        }
        catch(sys::filesystem_error& ex)
        {
-               error << string_compose(_("Could not create mix templates directory \"%1\" (%2)"),
+               error << string_compose(_("Could not create templates directory \"%1\" (%2)"),
                                user_template_dir.to_string(), ex.what()) << endmsg;
                return -1;
        }
 
        tree.set_root (&get_template());
 
-       sys::path template_file_path(user_template_dir);
-       template_file_path /= template_name + template_suffix;
-
-       if (sys::exists (template_file_path))
+       sys::path template_dir_path(user_template_dir);
+       
+       /* directory to put the template in */
+       template_dir_path /= template_name;
+       if (sys::exists (template_dir_path))
        {
                warning << string_compose(_("Template \"%1\" already exists - new version not created"),
-                               template_file_path.to_string()) << endmsg;
+                               template_dir_path.to_string()) << endmsg;
                return -1;
        }
+       
+       sys::create_directories (template_dir_path);
+
+       /* file to write */
+       sys::path template_file_path = template_dir_path;
+       template_file_path /= template_name + template_suffix;
 
        if (!tree.write (template_file_path.to_string())) {
                error << _("template not saved") << endmsg;
                return -1;
        }
 
-       return 0;
-}
-
-int
-Session::rename_template (string old_name, string new_name)
-{
-       sys::path old_path (user_template_directory());
-       old_path /= old_name + template_suffix;
-
-       sys::path new_path(user_template_directory());
-       new_path /= new_name + template_suffix;
-
-       if (sys::exists (new_path)) {
-               warning << string_compose(_("Template \"%1\" already exists - template not renamed"),
-                                         new_path.to_string()) << endmsg;
-               return -1;
-       }
-
-       try {
-               sys::rename (old_path, new_path);
-               return 0;
-       } catch (...) {
-               return -1;
-       }
-}
+       /* copy plugin state directory */
 
-int
-Session::delete_template (string name)
-{
-       sys::path path = user_template_directory();
-       path /= name + template_suffix;
+       sys::path template_plugin_state_path = template_dir_path;
+       template_plugin_state_path /= X_("plugins");
+       sys::create_directories (template_plugin_state_path);
+       sys::copy_files (plugins_dir(), template_plugin_state_path);
 
-       try {
-               sys::remove (path);
-               return 0;
-       } catch (...) {
-               return -1;
-       }
+       return 0;
 }
 
 void
 Session::refresh_disk_space ()
 {
-#if HAVE_SYS_VFS_H
-       struct statfs statfsbuf;
-       vector<space_and_path>::iterator i;
+#if HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H
+       
        Glib::Mutex::Lock lm (space_lock);
-       double scale;
 
        /* get freespace on every FS that is part of the session path */
 
        _total_free_4k_blocks = 0;
+       _total_free_4k_blocks_uncertain = false;
 
-       for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
-               statfs ((*i).path.c_str(), &statfsbuf);
+       for (vector<space_and_path>::iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
 
-               scale = statfsbuf.f_bsize/4096.0;
+               struct statfs statfsbuf;
+               statfs (i->path.c_str(), &statfsbuf);
 
-               (*i).blocks = (uint32_t) floor (statfsbuf.f_bavail * scale);
-               _total_free_4k_blocks += (*i).blocks;
+               double const scale = statfsbuf.f_bsize / 4096.0;
+
+               /* See if this filesystem is read-only */
+               struct statvfs statvfsbuf;
+               statvfs (i->path.c_str(), &statvfsbuf);
+
+               /* f_bavail can be 0 if it is undefined for whatever
+                  filesystem we are looking at; Samba shares mounted
+                  via GVFS are an example of this.
+               */
+               if (statfsbuf.f_bavail == 0) {
+                       /* block count unknown */
+                       i->blocks = 0;
+                       i->blocks_unknown = true;
+               } else if (statvfsbuf.f_flag & ST_RDONLY) {
+                       /* read-only filesystem */
+                       i->blocks = 0;
+                       i->blocks_unknown = false;
+               } else {
+                       /* read/write filesystem with known space */
+                       i->blocks = (uint32_t) floor (statfsbuf.f_bavail * scale);
+                       i->blocks_unknown = false;
+               }
+
+               _total_free_4k_blocks += i->blocks;
+               if (i->blocks_unknown) {
+                       _total_free_4k_blocks_uncertain = true;
+               }
        }
 #endif
 }
@@ -2111,7 +2134,7 @@ string
 Session::get_best_session_directory_for_new_source ()
 {
        vector<space_and_path>::iterator i;
-       string result = _session_dir->root_path().to_string();
+       string result = _session_dir->root_path();
 
        /* handle common case without system calls */
 
@@ -2254,6 +2277,12 @@ Session::plugins_dir () const
        return Glib::build_filename (_path, "plugins");
 }
 
+string
+Session::externals_dir () const
+{
+       return Glib::build_filename (_path, "externals");
+}
+
 int
 Session::load_bundles (XMLNode const & node)
 {
@@ -2370,8 +2399,9 @@ Session::add_route_group (RouteGroup* g)
        _route_groups.push_back (g);
        route_group_added (g); /* EMIT SIGNAL */
 
-       g->MembershipChanged.connect_same_thread (*this, boost::bind (&Session::route_group_changed, this));
-       g->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::route_group_changed, this));
+       g->RouteAdded.connect_same_thread (*this, boost::bind (&Session::route_added_to_route_group, this, _1, _2));
+       g->RouteRemoved.connect_same_thread (*this, boost::bind (&Session::route_removed_from_route_group, this, _1, _2));
+       g->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::route_group_property_changed, this, g));
 
        set_dirty ();
 }
@@ -2642,16 +2672,10 @@ Session::cleanup_regions ()
 
        for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
 
-               boost::shared_ptr<AudioRegion> audio_region = boost::dynamic_pointer_cast<AudioRegion>( i->second);
-
-               if (!audio_region) {
-                       continue;
-               }
-
-               uint32_t used = playlists->region_use_count (audio_region);
+               uint32_t used = playlists->region_use_count (i->second);
 
-               if (used == 0 && !audio_region->automatic()) {
-                       RegionFactory::map_remove(i->second);
+               if (used == 0 && !i->second->automatic ()) {
+                       RegionFactory::map_remove (i->second);
                }
        }
 
@@ -2726,7 +2750,7 @@ Session::cleanup_sources (CleanupReport& rep)
                ++nexti;
 
                SessionDirectory sdir ((*i).path);
-               audio_path += sdir.sound_path().to_string();
+               audio_path += sdir.sound_path();
 
                if (nexti != session_dirs.end()) {
                        audio_path += ':';
@@ -2744,7 +2768,7 @@ Session::cleanup_sources (CleanupReport& rep)
                ++nexti;
 
                SessionDirectory sdir ((*i).path);
-               midi_path += sdir.midi_path().to_string();
+               midi_path += sdir.midi_path();
 
                if (nexti != session_dirs.end()) {
                        midi_path += ':';
@@ -2935,7 +2959,7 @@ Session::cleanup_sources (CleanupReport& rep)
                                                          peakpath, _path, strerror (errno))
                                      << endmsg;
                                /* try to back out */
-                               rename (newpath.c_str(), _path.c_str());
+                               ::rename (newpath.c_str(), _path.c_str());
                                goto out;
                        }
                }
@@ -3039,7 +3063,7 @@ struct null_deleter { void operator()(void const *) const {} };
 void
 Session::remove_controllable (Controllable* c)
 {
-       if (_state_of_the_state | Deletion) {
+       if (_state_of_the_state & Deletion) {
                return;
        }
 
@@ -3228,8 +3252,8 @@ Session::save_history (string snapshot_name)
 
        const string history_filename = legalize_for_path (snapshot_name) + history_suffix;
        const string backup_filename = history_filename + backup_suffix;
-       const sys::path xml_path = _session_dir->root_path() / history_filename;
-       const sys::path backup_path = _session_dir->root_path() / backup_filename;
+       const sys::path xml_path(Glib::build_filename (_session_dir->root_path(), history_filename));
+       const sys::path backup_path(Glib::build_filename (_session_dir->root_path(), backup_filename));
 
        if (sys::exists (xml_path)) {
                try
@@ -3280,7 +3304,7 @@ Session::restore_history (string snapshot_name)
        }
 
        const string xml_filename = legalize_for_path (snapshot_name) + history_suffix;
-       const sys::path xml_path = _session_dir->root_path() / xml_filename;
+       const sys::path xml_path(Glib::build_filename (_session_dir->root_path(), xml_filename));
 
        info << "Loading history from " << xml_path.to_string() << endmsg;
 
@@ -3435,11 +3459,11 @@ Session::config_changed (std::string p, bool ours)
 
                //poke_midi_thread ();
 
-       } else if (p == "mmc-device-id" || p == "mmc-receive-id") {
+       } else if (p == "mmc-device-id" || p == "mmc-receive-id" || p == "mmc-receive-device-id") {
 
                MIDI::Manager::instance()->mmc()->set_receive_device_id (Config->get_mmc_receive_device_id());
 
-       } else if (p == "mmc-send-id") {
+       } else if (p == "mmc-send-id" || p == "mmc-send-device-id") {
 
                MIDI::Manager::instance()->mmc()->set_send_device_id (Config->get_mmc_send_device_id());
 
@@ -3489,6 +3513,12 @@ Session::config_changed (std::string p, bool ours)
                        _clicking = false;
                }
 
+       } else if (p == "click-gain") {
+               
+               if (_click_gain) {
+                       _click_gain->set_gain (Config->get_click_gain(), this);
+               }
+
        } else if (p == "send-mtc") {
 
                if (Config->get_send_mtc ()) {
@@ -3558,6 +3588,8 @@ Session::config_changed (std::string p, bool ours)
                last_timecode_valid = false;
        } else if (p == "playback-buffer-seconds") {
                AudioSource::allocate_working_buffers (frame_rate());
+       } else if (p == "automation-thinning-factor") {
+               Evoral::ControlList::set_thinning_factor (Config->get_automation_thinning_factor());
        }
 
        set_dirty ();
@@ -3620,9 +3652,9 @@ Session::setup_midi_machine_control ()
 
        /* also handle MIDI SPP because its so common */
 
-       mmc->SPPStart.connect_same_thread (*this, boost::bind (&Session::spp_start, this, _1, _2));
-       mmc->SPPContinue.connect_same_thread (*this, boost::bind (&Session::spp_continue, this, _1, _2));
-       mmc->SPPStop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this, _1, _2));
+       mmc->SPPStart.connect_same_thread (*this, boost::bind (&Session::spp_start, this));
+       mmc->SPPContinue.connect_same_thread (*this, boost::bind (&Session::spp_continue, this));
+       mmc->SPPStop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this));
 }
 
 boost::shared_ptr<Controllable>
@@ -3639,3 +3671,170 @@ Session::solo_cut_control() const
 
         return _solo_cut_control;
 }
+
+int
+Session::rename (const std::string& new_name)
+{
+       string legal_name = legalize_for_path (new_name);
+       string newpath;
+       string oldstr;
+       string newstr;
+       bool first = true;
+
+       string const old_sources_root = _session_dir->sources_root();
+
+#define RENAME ::rename
+
+       /* Rename:
+
+        * session directory
+        * interchange subdirectory
+        * session file
+        * session history
+        
+        * Backup files are left unchanged and not renamed.
+        */
+
+       /* pass one: not 100% safe check that the new directory names don't
+        * already exist ...
+        */
+
+       for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+               vector<string> v;
+
+               oldstr = (*i).path;
+
+               /* this is a stupid hack because Glib::path_get_dirname() is
+                * lexical-only, and so passing it /a/b/c/ gives a different
+                * result than passing it /a/b/c ...
+                */
+
+               if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
+                       oldstr = oldstr.substr (0, oldstr.length() - 1);
+               }
+
+               string base = Glib::path_get_dirname (oldstr);
+               string p = Glib::path_get_basename (oldstr);
+
+               newstr = Glib::build_filename (base, legal_name);
+               
+               if (Glib::file_test (newstr, Glib::FILE_TEST_EXISTS)) {
+                       return -1;
+               }
+       }
+
+       /* Session dirs */
+       
+       for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+               vector<string> v;
+
+               oldstr = (*i).path;
+
+               /* this is a stupid hack because Glib::path_get_dirname() is
+                * lexical-only, and so passing it /a/b/c/ gives a different
+                * result than passing it /a/b/c ...
+                */
+
+               if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
+                       oldstr = oldstr.substr (0, oldstr.length() - 1);
+               }
+
+               string base = Glib::path_get_dirname (oldstr);
+               string p = Glib::path_get_basename (oldstr);
+
+               newstr = Glib::build_filename (base, legal_name);
+
+               cerr << "Rename " << oldstr << " => " << newstr << endl;                
+
+               if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+                       return 1;
+               }
+
+               if (first) {
+                       (*_session_dir) = newstr;
+                       newpath = newstr;
+                       first = 1;
+               }
+
+               /* directory below interchange */
+
+               v.push_back (newstr);
+               v.push_back (interchange_dir_name);
+               v.push_back (p);
+
+               oldstr = Glib::build_filename (v);
+
+               v.clear ();
+               v.push_back (newstr);
+               v.push_back (interchange_dir_name);
+               v.push_back (legal_name);
+
+               newstr = Glib::build_filename (v);
+               
+               cerr << "Rename " << oldstr << " => " << newstr << endl;
+               
+               if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+                       return 1;
+               }
+       }
+
+       /* state file */
+       
+       oldstr = Glib::build_filename (newpath, _current_snapshot_name) + statefile_suffix;
+       newstr= Glib::build_filename (newpath, legal_name) + statefile_suffix;
+       
+       cerr << "Rename " << oldstr << " => " << newstr << endl;                
+
+       if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+               return 1;
+       }
+
+       /* history file */
+
+       
+       oldstr = Glib::build_filename (newpath, _current_snapshot_name) + history_suffix;
+
+       if (Glib::file_test (oldstr, Glib::FILE_TEST_EXISTS))  {
+               newstr = Glib::build_filename (newpath, legal_name) + history_suffix;
+               
+               cerr << "Rename " << oldstr << " => " << newstr << endl;                
+               
+               if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+                       return 1;
+               }
+       }
+
+       /* update file source paths */
+       
+       for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+               boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
+               if (fs) {
+                       string p = fs->path ();
+                       boost::replace_all (p, old_sources_root, _session_dir->sources_root());
+                       fs->set_path (p);
+               }
+       }
+
+       /* remove old name from recent sessions */
+
+       remove_recent_sessions (_path);
+
+       _path = newpath;
+       _current_snapshot_name = new_name;
+       _name = new_name;
+
+       set_dirty ();
+
+       /* save state again to get everything just right */
+
+       save_state (_current_snapshot_name);
+
+
+       /* add to recent sessions */
+
+       store_recent_sessions (new_name, _path);
+
+       return 0;
+
+#undef RENAME
+}