Mixbus specific Pin Mapping tweaks
[ardour.git] / libs / ardour / session_state.cc
index c514aa9861b077abc8ea010ae90cfed2d151cbf4..95eb36c07f4ec6fb617d12f20b214a8fac01f438 100644 (file)
 #include <stdint.h>
 
 #include <algorithm>
-#include <fstream>
 #include <string>
 #include <cerrno>
 #include <cstdio> /* snprintf(3) ... grrr */
 #include <cmath>
 #include <unistd.h>
-#include <sys/stat.h>
 #include <climits>
 #include <signal.h>
 #include <sys/time.h>
 #endif
 
 #include <glib.h>
-#include <glib/gstdio.h>
+#include "pbd/gstdio_compat.h"
 
 #include <glibmm.h>
 #include <glibmm/threads.h>
+#include <glibmm/fileutils.h>
 
 #include <boost/algorithm/string.hpp>
 
@@ -65,6 +64,7 @@
 #include "pbd/boost_debug.h"
 #include "pbd/basename.h"
 #include "pbd/controllable_descriptor.h"
+#include "pbd/debug.h"
 #include "pbd/enumwriter.h"
 #include "pbd/error.h"
 #include "pbd/file_utils.h"
@@ -73,6 +73,7 @@
 #include "pbd/stacktrace.h"
 #include "pbd/convert.h"
 #include "pbd/localtime_r.h"
+#include "pbd/unwind.h"
 
 #include "ardour/amp.h"
 #include "ardour/async_midi_port.h"
 #include "ardour/playlist_source.h"
 #include "ardour/port.h"
 #include "ardour/processor.h"
+#include "ardour/profile.h"
 #include "ardour/proxy_controllable.h"
 #include "ardour/recent_sessions.h"
 #include "ardour/region_factory.h"
 
 #include "control_protocol/control_protocol.h"
 
+#include "LuaBridge/LuaBridge.h"
+
 #include "i18n.h"
 #include <locale.h>
 
@@ -127,6 +131,8 @@ using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
 
+#define DEBUG_UNDO_HISTORY(msg) DEBUG_TRACE (PBD::DEBUG::UndoHistory, string_compose ("%1: %2\n", __LINE__, msg));
+
 void
 Session::pre_engine_init (string fullpath)
 {
@@ -139,15 +145,19 @@ Session::pre_engine_init (string fullpath)
 
        _path = canonical_path(fullpath);
 
-       /* we require _path to end with a dir separator */
-
-       if (_path[_path.length()-1] != G_DIR_SEPARATOR) {
-               _path += G_DIR_SEPARATOR;
-       }
-
        /* is it new ? */
+       if (Profile->get_trx() ) {
+               // Waves TracksLive has a usecase of session replacement with a new one.
+               // We should check session state file (<session_name>.ardour) existance
+               // to determine if the session is new or not
+
+               string full_session_name = Glib::build_filename( fullpath, _name );
+               full_session_name += statefile_suffix;
 
-       _is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
+               _is_new = !Glib::file_test (full_session_name, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
+       } else {
+               _is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
+       }
 
        /* finish initialization that can't be done in a normal C++ constructor
           definition.
@@ -171,7 +181,7 @@ Session::pre_engine_init (string fullpath)
        last_rr_session_dir = session_dirs.begin();
 
        set_history_depth (Config->get_history_depth());
-       
+
         /* default: assume simple stereo speaker configuration */
 
         _speakers->setup_default_speakers (2);
@@ -193,8 +203,6 @@ Session::pre_engine_init (string fullpath)
 
        Delivery::disable_panners ();
        IO::disable_connecting ();
-
-       AudioFileSource::set_peak_dir (_session_dir->peak_path());
 }
 
 int
@@ -208,26 +216,26 @@ Session::post_engine_init ()
        BootMessage (_("Using configuration"));
 
        _midi_ports = new MidiPortManager;
-       
+
        MIDISceneChanger* msc;
 
        _scene_changer = msc = new MIDISceneChanger (*this);
-       msc->set_input_port (scene_input_port());
-       msc->set_output_port (scene_out());
+       msc->set_input_port (boost::dynamic_pointer_cast<MidiPort>(scene_input_port()));
+       msc->set_output_port (boost::dynamic_pointer_cast<MidiPort>(scene_output_port()));
 
        boost::function<framecnt_t(void)> timer_func (boost::bind (&Session::audible_frame, this));
-       boost::dynamic_pointer_cast<AsyncMIDIPort>(scene_in())->set_timer (timer_func);
+       boost::dynamic_pointer_cast<AsyncMIDIPort>(scene_input_port())->set_timer (timer_func);
 
        setup_midi_machine_control ();
-       
+
        if (_butler->start_thread()) {
                return -1;
        }
-       
+
        if (start_midi_thread ()) {
                return -1;
        }
-       
+
        setup_click_sounds (0);
        setup_midi_control ();
 
@@ -240,7 +248,7 @@ Session::post_engine_init ()
                delete _tempo_map;
                _tempo_map = new TempoMap (_current_frame_rate);
                _tempo_map->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1));
-               
+
                /* MidiClock requires a tempo map */
 
                midi_clock = new MidiClockTicker ();
@@ -250,14 +258,14 @@ Session::post_engine_init ()
 
                SndFileSource::setup_standard_crossfades (*this, frame_rate());
                _engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this));
-               
+
                AudioDiskstream::allocate_working_buffers();
                refresh_disk_space ();
-               
+
                /* we're finally ready to call set_state() ... all objects have
                 * been created, the engine is running.
                 */
-               
+
                if (state_tree) {
                        if (set_state (*state_tree->root(), Stateful::loading_state_version)) {
                                return -1;
@@ -272,55 +280,56 @@ Session::post_engine_init ()
 
                boost::function<void (std::string)> ff (boost::bind (&Session::config_changed, this, _1, false));
                boost::function<void (std::string)> ft (boost::bind (&Session::config_changed, this, _1, true));
-               
+
                Config->map_parameters (ff);
                config.map_parameters (ft);
+                _butler->map_parameters ();
 
                /* Reset all panners */
-               
+
                Delivery::reset_panners ();
-               
+
                /* this will cause the CPM to instantiate any protocols that are in use
                 * (or mandatory), which will pass it this Session, and then call
                 * set_state() on each instantiated protocol to match stored state.
                 */
-               
+
                ControlProtocolManager::instance().set_session (this);
-               
+
                /* This must be done after the ControlProtocolManager set_session above,
                   as it will set states for ports which the ControlProtocolManager creates.
                */
-               
+
                // XXX set state of MIDI::Port's
                // MidiPortManager::instance()->set_port_states (Config->midi_port_states ());
-               
+
                /* And this must be done after the MIDI::Manager::set_port_states as
                 * it will try to make connections whose details are loaded by set_port_states.
                 */
-               
+
                hookup_io ();
-               
+
                /* Let control protocols know that we are now all connected, so they
                 * could start talking to surfaces if they want to.
                 */
-               
+
                ControlProtocolManager::instance().midi_connectivity_established ();
-               
+
                if (_is_new && !no_auto_connect()) {
                        Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock());
                        auto_connect_master_bus ();
                }
-               
+
                _state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty));
-               
+
                /* update latencies */
-               
+
                initialize_latencies ();
-               
+
                _locations->added.connect_same_thread (*this, boost::bind (&Session::location_added, this, _1));
                _locations->removed.connect_same_thread (*this, boost::bind (&Session::location_removed, this, _1));
                _locations->changed.connect_same_thread (*this, boost::bind (&Session::locations_changed, this));
-               
+
        } catch (AudioEngine::PortRegistrationFailure& err) {
                /* handle this one in a different way than all others, so that its clear what happened */
                error << err.what() << endmsg;
@@ -337,7 +346,7 @@ Session::post_engine_init ()
        send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset));
        send_immediate_mmc (MIDI::MachineControlCommand (Timecode::Time ()));
 
-       MIDI::Name::MidiPatchManager::instance().set_session (this);
+       MIDI::Name::MidiPatchManager::instance().add_search_path (session_directory().midi_patch_path() );
 
        ltc_tx_initialize();
        /* initial program change will be delivered later; see ::config_changed() */
@@ -356,9 +365,44 @@ Session::post_engine_init ()
                state_was_pending = false;
        }
 
+       /* Now, finally, we can fill the playback buffers */
+
+       BootMessage (_("Filling playback buffers"));
+
+       boost::shared_ptr<RouteList> rl = routes.reader();
+       for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
+               boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<Track> (*r);
+               if (trk && !trk->hidden()) {
+                       trk->seek (_transport_frame, true);
+               }
+       }
+
        return 0;
 }
 
+void
+Session::session_loaded ()
+{
+       SessionLoaded();
+
+       _state_of_the_state = Clean;
+
+       DirtyChanged (); /* EMIT SIGNAL */
+
+       if (_is_new) {
+               save_state ("");
+       } else if (state_was_pending) {
+               save_state ("");
+               remove_pending_capture_state ();
+               state_was_pending = false;
+       }
+
+       /* Now, finally, we can fill the playback buffers */
+
+       BootMessage (_("Filling playback buffers"));
+       force_locate (_transport_frame, false);
+}
+
 string
 Session::raid_path () const
 {
@@ -495,9 +539,9 @@ Session::create (const string& session_template, BusProfile* bus_profile)
        _writable = exists_and_writable (_path);
 
        if (!session_template.empty()) {
-               std::string in_path = session_template_dir_to_file (session_template);
+               string in_path = (ARDOUR::Profile->get_trx () ? session_template : session_template_dir_to_file (session_template));
 
-               ifstream in(in_path.c_str());
+               FILE* in = g_fopen (in_path.c_str(), "rb");
 
                if (in) {
                        /* no need to call legalize_for_path() since the string
@@ -505,21 +549,51 @@ Session::create (const string& session_template, BusProfile* bus_profile)
                         */
                        string out_path = Glib::build_filename (_session_dir->root_path(), _name + statefile_suffix);
 
-                       ofstream out(out_path.c_str());
+                       FILE* out = g_fopen (out_path.c_str(), "wb");
 
                        if (out) {
-                               out << in.rdbuf();
-                                _is_new = false;
+                               char buf[1024];
+                               stringstream new_session;
+
+                               while (!feof (in)) {
+                                       size_t charsRead = fread (buf, sizeof(char), 1024, in);
+
+                                       if (ferror (in)) {
+                                               error << string_compose (_("Error reading session template file %1 (%2)"), in_path, strerror (errno)) << endmsg;
+                                               fclose (in);
+                                               fclose (out);
+                                               return -1;
+                                       }
+                                       if (charsRead == 0) {
+                                               break;
+                                       }
+                                       new_session.write (buf, charsRead);
+                               }
+                               fclose (in);
+
+                               string file_contents = new_session.str();
+                               size_t writeSize = file_contents.length();
+                               if (fwrite (file_contents.c_str(), sizeof(char), writeSize, out) != writeSize) {
+                                       error << string_compose (_("Error writing session template file %1 (%2)"), out_path, strerror (errno)) << endmsg;
+                                       fclose (out);
+                                       return -1;
+                               }
+                               fclose (out);
+
+                               _is_new = false;
+
+                               if (!ARDOUR::Profile->get_trx()) {
+                                       /* Copy plugin state files from template to new session */
+                                       std::string template_plugins = Glib::build_filename (session_template, X_("plugins"));
+                                       copy_recurse (template_plugins, plugins_dir ());
+                               }
 
-                               /* Copy plugin state files from template to new session */
-                               std::string template_plugins = Glib::build_filename (session_template, X_("plugins"));
-                               copy_recurse (template_plugins, plugins_dir ());
-                               
                                return 0;
 
                        } else {
                                error << string_compose (_("Could not open %1 for writing session template"), out_path)
                                        << endmsg;
+                               fclose(in);
                                return -1;
                        }
 
@@ -531,7 +605,21 @@ Session::create (const string& session_template, BusProfile* bus_profile)
 
        }
 
-       /* set initial start + end point */
+       if (Profile->get_trx()) {
+
+               /* set initial start + end point : ARDOUR::Session::session_end_shift long.
+                  Remember that this is a brand new session. Sessions
+                  loaded from saved state will get this range from the saved state.
+               */
+
+               set_session_range_location (0, 0);
+
+               /* Initial loop location, from absolute zero, length 10 seconds  */
+
+               Location* loc = new Location (*this, 0, 10.0 * _engine.sample_rate(), _("Loop"),  Location::IsAutoLoop);
+               _locations->add (loc, true);
+               set_auto_loop_location (loc);
+       }
 
        _state_of_the_state = Clean;
 
@@ -542,8 +630,9 @@ Session::create (const string& session_template, BusProfile* bus_profile)
                RouteList rl;
                 ChanCount count(DataType::AUDIO, bus_profile->master_out_channels);
 
-               if (bus_profile->master_out_channels) {
-                       boost::shared_ptr<Route> r (new Route (*this, _("Master"), Route::MasterOut, DataType::AUDIO));
+                // Waves Tracks: always create master bus for Tracks
+                if (ARDOUR::Profile->get_trx() || bus_profile->master_out_channels) {
+                       boost::shared_ptr<Route> r (new Route (*this, _("Master"), Route::MasterOut, DataType::AUDIO));
                         if (r->init ()) {
                                 return -1;
                         }
@@ -567,16 +656,20 @@ Session::create (const string& session_template, BusProfile* bus_profile)
                        add_routes (rl, false, false, false);
                }
 
-                /* this allows the user to override settings with an environment variable.
-                 */
+               // Waves Tracks: Skip this. Always use autoconnection for Tracks
+               if (!ARDOUR::Profile->get_trx()) {
 
-                if (no_auto_connect()) {
-                        bus_profile->input_ac = AutoConnectOption (0);
-                        bus_profile->output_ac = AutoConnectOption (0);
-                }
+                       /* this allows the user to override settings with an environment variable.
+                        */
 
-                Config->set_input_auto_connect (bus_profile->input_ac);
-                Config->set_output_auto_connect (bus_profile->output_ac);
+                       if (no_auto_connect()) {
+                               bus_profile->input_ac = AutoConnectOption (0);
+                               bus_profile->output_ac = AutoConnectOption (0);
+                       }
+
+                       Config->set_input_auto_connect (bus_profile->input_ac);
+                       Config->set_output_auto_connect (bus_profile->output_ac);
+               }
         }
 
        if (Config->get_use_monitor_bus() && bus_profile) {
@@ -663,7 +756,7 @@ Session::remove_state (string snapshot_name)
 
 /** @param snapshot_name Name to save under, without .ardour / .pending prefix */
 int
-Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot)
+Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot, bool template_only)
 {
        XMLTree tree;
        std::string xml_path(_session_dir->root_path());
@@ -689,6 +782,10 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
                return 1;
        }
 
+#ifndef NDEBUG
+       const int64_t save_start_time = g_get_monotonic_time();
+#endif
+
        /* 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) {
@@ -701,13 +798,26 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
 
        SessionSaveUnderway (); /* EMIT SIGNAL */
 
-       tree.set_root (&get_state());
+       bool mark_as_clean = true;
+
+       if (!snapshot_name.empty() && !switch_to_snapshot) {
+               mark_as_clean = false;
+       }
+
+       if (template_only) {
+               mark_as_clean = false;
+               tree.set_root (&get_template());
+       } else {
+               tree.set_root (&get_state());
+       }
 
        if (snapshot_name.empty()) {
                snapshot_name = _current_snapshot_name;
        } else if (switch_to_snapshot) {
-                _current_snapshot_name = snapshot_name;
-        }
+               set_snapshot_name (snapshot_name);
+       }
+
+       assert (!snapshot_name.empty());
 
        if (!pending) {
 
@@ -731,7 +841,7 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
        std::string tmp_path(_session_dir->root_path());
        tmp_path = Glib::build_filename (tmp_path, legalize_for_path (snapshot_name) + temp_suffix);
 
-       // cerr << "actually writing state to " << xml_path << endl;
+       cerr << "actually writing state to " << tmp_path << endl;
 
        if (!tree.write (tmp_path)) {
                error << string_compose (_("state could not be saved to %1"), tmp_path) << endmsg;
@@ -743,6 +853,8 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
 
        } else {
 
+               cerr << "renaming state to " << xml_path << endl;
+
                if (::g_rename (tmp_path.c_str(), xml_path.c_str()) != 0) {
                        error << string_compose (_("could not rename temporary session file %1 to %2 (%3)"),
                                        tmp_path, xml_path, g_strerror(errno)) << endmsg;
@@ -758,17 +870,23 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
 
                save_history (snapshot_name);
 
-               bool was_dirty = dirty();
+               if (mark_as_clean) {
+                       bool was_dirty = dirty();
 
-               _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
+                       _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
 
-               if (was_dirty) {
-                       DirtyChanged (); /* EMIT SIGNAL */
+                       if (was_dirty) {
+                               DirtyChanged (); /* EMIT SIGNAL */
+                       }
                }
 
                StateSaved (snapshot_name); /* EMIT SIGNAL */
        }
 
+#ifndef NDEBUG
+       const int64_t elapsed_time_us = g_get_monotonic_time() - save_start_time;
+       cerr << "saved state in " << fixed << setprecision (1) << elapsed_time_us / 1000. << " ms\n";
+#endif
        return 0;
 }
 
@@ -866,15 +984,17 @@ Session::load_state (string snapshot_name)
                // only create a backup for a given statefile version once
 
                if (!Glib::file_test (backup_path, Glib::FILE_TEST_EXISTS)) {
-                       
+
                        VersionMismatch (xmlpath, backup_path);
-                       
+
                        if (!copy_file (xmlpath, backup_path)) {;
                                return -1;
                        }
                }
        }
 
+       save_snapshot_name (snapshot_name);
+
        return 0;
 }
 
@@ -926,7 +1046,7 @@ Session::state (bool full_state)
        if (full_state) {
 
                node->add_property ("name", _name);
-               snprintf (buf, sizeof (buf), "%" PRId64, _nominal_frame_rate);
+               snprintf (buf, sizeof (buf), "%" PRId64, _base_frame_rate);
                node->add_property ("sample-rate", buf);
 
                if (session_dirs.size() > 1) {
@@ -964,6 +1084,9 @@ Session::state (bool full_state)
        snprintf (buf, sizeof (buf), "%" PRIu64, ID::counter());
        node->add_property ("id-counter", buf);
 
+       snprintf (buf, sizeof (buf), "%u", name_id_counter ());
+       node->add_property ("name-counter", buf);
+
        /* save the event ID counter */
 
        snprintf (buf, sizeof (buf), "%d", Evoral::event_id_counter());
@@ -1045,16 +1168,31 @@ Session::state (bool full_state)
                }
        }
 
+
+
        if (full_state) {
-               node->add_child_nocopy (_locations->get_state());
+
+               if (_locations) {
+                       node->add_child_nocopy (_locations->get_state());
+               }
        } else {
+               Locations loc (*this);
                // for a template, just create a new Locations, populate it
                // with the default start and end, and get the state for that.
-               Locations loc (*this);
                Location* range = new Location (*this, 0, 0, _("session"), Location::IsSessionRange);
                range->set (max_framepos, 0);
                loc.add (range);
-               node->add_child_nocopy (loc.get_state());
+               XMLNode& locations_state = loc.get_state();
+
+               if (ARDOUR::Profile->get_trx() && _locations) {
+                       // For tracks we need stored the Auto Loop Range and all MIDI markers.
+                       for (Locations::LocationList::const_iterator i = _locations->list ().begin (); i != _locations->list ().end (); ++i) {
+                               if ((*i)->is_mark () || (*i)->is_auto_loop ()) {
+                                       locations_state.add_child_nocopy ((*i)->get_state ());
+                               }
+                       }
+               }
+               node->add_child_nocopy (locations_state);
        }
 
        child = node->add_child ("Bundles");
@@ -1076,11 +1214,11 @@ Session::state (bool full_state)
                RouteList public_order (*r);
                public_order.sort (cmp);
 
-                /* the sort should have put control outs first */
+               /* the sort should have put control outs first */
 
-                if (_monitor_out) {
-                        assert (_monitor_out == public_order.front());
-                }
+               if (_monitor_out) {
+                       assert (_monitor_out == public_order.front());
+               }
 
                for (RouteList::iterator i = public_order.begin(); i != public_order.end(); ++i) {
                        if (!(*i)->is_auditioner()) {
@@ -1124,6 +1262,26 @@ Session::state (bool full_state)
                node->add_child_copy (*_extra_xml);
        }
 
+       {
+               Glib::Threads::Mutex::Lock lm (lua_lock);
+               std::string saved;
+               {
+                       luabridge::LuaRef savedstate ((*_lua_save)());
+                       saved = savedstate.cast<std::string>();
+               }
+               lua.collect_garbage ();
+               lm.release ();
+
+               gchar* b64 = g_base64_encode ((const guchar*)saved.c_str (), saved.size ());
+               std::string b64s (b64);
+               g_free (b64);
+
+               XMLNode* script_node = new XMLNode (X_("Script"));
+               script_node->add_property (X_("lua"), LUA_VERSION);
+               script_node->add_content (b64s);
+               node->add_child_nocopy (*script_node);
+       }
+
        return *node;
 }
 
@@ -1155,10 +1313,12 @@ Session::set_state (const XMLNode& node, int version)
 
        if ((prop = node.property (X_("sample-rate"))) != 0) {
 
-               _nominal_frame_rate = atoi (prop->value());
+               _base_frame_rate = atoi (prop->value());
+               _nominal_frame_rate = _base_frame_rate;
 
-               if (_nominal_frame_rate != _current_frame_rate) {
-                        boost::optional<int> r = AskAboutSampleRateMismatch (_nominal_frame_rate, _current_frame_rate);
+               assert (AudioEngine::instance()->running ());
+               if (_base_frame_rate != AudioEngine::instance()->sample_rate ()) {
+                       boost::optional<int> r = AskAboutSampleRateMismatch (_base_frame_rate, _current_frame_rate);
                        if (r.get_value_or (0)) {
                                goto out;
                        }
@@ -1181,10 +1341,13 @@ Session::set_state (const XMLNode& node, int version)
                ID::init_counter (now);
        }
 
-        if ((prop = node.property (X_("event-counter"))) != 0) {
-                Evoral::init_event_id_counter (atoi (prop->value()));
-        }
+       if ((prop = node.property (X_("name-counter"))) != 0) {
+               init_name_id_counter (atoi (prop->value()));
+       }
 
+       if ((prop = node.property (X_("event-counter"))) != 0) {
+               Evoral::init_event_id_counter (atoi (prop->value()));
+       }
 
        if ((child = find_named_node (node, "MIDIPorts")) != 0) {
                _midi_ports->set_midi_port_states (child->children());
@@ -1334,9 +1497,25 @@ Session::set_state (const XMLNode& node, int version)
                ControlProtocolManager::instance().set_state (*child, version);
        }
 
-       update_have_rec_enabled_track ();
+       if ((child = find_named_node (node, "Script"))) {
+               for (XMLNodeList::const_iterator n = child->children ().begin (); n != child->children ().end (); ++n) {
+                       if (!(*n)->is_content ()) { continue; }
+                       gsize size;
+                       guchar* buf = g_base64_decode ((*n)->content ().c_str (), &size);
+                       try {
+                               Glib::Threads::Mutex::Lock lm (lua_lock);
+                               (*_lua_load)(std::string ((const char*)buf, size));
+                       } catch (luabridge::LuaException const& e) {
+                               cerr << "LuaException:" << e.what () << endl;
+                       }
+                       g_free (buf);
+               }
+       }
+
+       update_route_record_state ();
 
        /* here beginneth the second phase ... */
+       set_snapshot_name (_current_snapshot_name);
 
        StateReady (); /* EMIT SIGNAL */
 
@@ -1606,7 +1785,7 @@ Session::load_nested_sources (const XMLNode& node)
        for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
                if ((*niter)->name() == "Source") {
 
-                       /* it may already exist, so don't recreate it unnecessarily 
+                       /* it may already exist, so don't recreate it unnecessarily
                         */
 
                        XMLProperty* prop = (*niter)->property (X_("id"));
@@ -1852,6 +2031,20 @@ Session::get_sources_as_xml ()
        return *node;
 }
 
+void
+Session::reset_write_sources (bool mark_write_complete, bool force)
+{
+       boost::shared_ptr<RouteList> rl = routes.reader();
+       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+               if (tr) {
+                       _state_of_the_state = StateOfTheState (_state_of_the_state|InCleanup);
+                       tr->reset_write_sources(mark_write_complete, force);
+                       _state_of_the_state = StateOfTheState (_state_of_the_state & ~InCleanup);
+               }
+       }
+}
+
 int
 Session::load_sources (const XMLNode& node)
 {
@@ -1966,62 +2159,73 @@ Session::XMLSourceFactory (const XMLNode& node)
 }
 
 int
-Session::save_template (string template_name)
+Session::save_template (string template_name, bool replace_existing)
 {
-       XMLTree tree;
-
-       if (_state_of_the_state & CannotSave) {
+       if ((_state_of_the_state & CannotSave) || template_name.empty ()) {
                return -1;
        }
 
-       std::string user_template_dir(user_template_directory());
+       bool absolute_path = Glib::path_is_absolute (template_name);
 
-       if (g_mkdir_with_parents (user_template_dir.c_str(), 0755) != 0) {
-               error << string_compose(_("Could not create templates directory \"%1\" (%2)"),
-                               user_template_dir, g_strerror (errno)) << endmsg;
-               return -1;
-       }
+       /* directory to put the template in */
+       std::string template_dir_path;
 
-       tree.set_root (&get_template());
+       if (!absolute_path) {
+               std::string user_template_dir(user_template_directory());
 
-       std::string template_dir_path(user_template_dir);
-       
-       /* directory to put the template in */
-       template_dir_path = Glib::build_filename (template_dir_path, template_name);
+               if (g_mkdir_with_parents (user_template_dir.c_str(), 0755) != 0) {
+                       error << string_compose(_("Could not create templates directory \"%1\" (%2)"),
+                                       user_template_dir, g_strerror (errno)) << endmsg;
+                       return -1;
+               }
 
-       if (Glib::file_test (template_dir_path, Glib::FILE_TEST_EXISTS)) {
-               warning << string_compose(_("Template \"%1\" already exists - new version not created"),
-                               template_dir_path) << endmsg;
-               return -1;
+               template_dir_path = Glib::build_filename (user_template_dir, template_name);
+       } else {
+               template_dir_path = template_name;
        }
-       
-       if (g_mkdir_with_parents (template_dir_path.c_str(), 0755) != 0) {
-               error << string_compose(_("Could not create directory for Session template\"%1\" (%2)"),
-                               template_dir_path, g_strerror (errno)) << endmsg;
-               return -1;
+
+       if (!ARDOUR::Profile->get_trx()) {
+               if (!replace_existing && Glib::file_test (template_dir_path, Glib::FILE_TEST_EXISTS)) {
+                       warning << string_compose(_("Template \"%1\" already exists - new version not created"),
+                                                                         template_dir_path) << endmsg;
+                       return -2;
+               }
+
+               if (g_mkdir_with_parents (template_dir_path.c_str(), 0755) != 0) {
+                       error << string_compose(_("Could not create directory for Session template\"%1\" (%2)"),
+                                                                       template_dir_path, g_strerror (errno)) << endmsg;
+                       return -1;
+               }
        }
 
        /* file to write */
-       std::string template_file_path(template_dir_path);
-       template_file_path = Glib::build_filename (template_file_path, template_name + template_suffix);
+       std::string template_file_path;
 
-       if (!tree.write (template_file_path)) {
-               error << _("template not saved") << endmsg;
-               return -1;
+       if (ARDOUR::Profile->get_trx()) {
+               template_file_path = template_name;
+       } else {
+               if (absolute_path) {
+                       template_file_path = Glib::build_filename (template_dir_path, Glib::path_get_basename (template_dir_path) + template_suffix);
+               } else {
+                       template_file_path = Glib::build_filename (template_dir_path, template_name + template_suffix);
+               }
        }
 
-       /* copy plugin state directory */
+       SessionSaveUnderway (); /* EMIT SIGNAL */
+
+       XMLTree tree;
 
-       std::string template_plugin_state_path(template_dir_path);
-       template_plugin_state_path = Glib::build_filename (template_plugin_state_path, X_("plugins"));
+       {
+               PBD::Unwinder<std::string> uw (_template_state_dir, template_dir_path);
+               tree.set_root (&get_template());
+       }
 
-       if (g_mkdir_with_parents (template_plugin_state_path.c_str(), 0755) != 0) {
-               error << string_compose(_("Could not create directory for Session template plugin state\"%1\" (%2)"),
-                               template_plugin_state_path, g_strerror (errno)) << endmsg;
+       if (!tree.write (template_file_path)) {
+               error << _("template not saved") << endmsg;
                return -1;
        }
 
-       copy_recurse (plugins_dir(), template_plugin_state_path);
+       store_recent_templates (template_file_path);
 
        return 0;
 }
@@ -2030,7 +2234,7 @@ void
 Session::refresh_disk_space ()
 {
 #if __APPLE__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
-       
+
        Glib::Threads::Mutex::Lock lm (space_lock);
 
        /* get freespace on every FS that is part of the session path */
@@ -2224,25 +2428,25 @@ Session::get_best_session_directory_for_new_audio ()
 string
 Session::automation_dir () const
 {
-       return Glib::build_filename (_path, "automation");
+       return Glib::build_filename (_path, automation_dir_name);
 }
 
 string
 Session::analysis_dir () const
 {
-       return Glib::build_filename (_path, "analysis");
+       return Glib::build_filename (_path, analysis_dir_name);
 }
 
 string
 Session::plugins_dir () const
 {
-       return Glib::build_filename (_path, "plugins");
+       return Glib::build_filename (_path, plugins_dir_name);
 }
 
 string
 Session::externals_dir () const
 {
-       return Glib::build_filename (_path, "externals");
+       return Glib::build_filename (_path, externals_dir_name);
 }
 
 int
@@ -2408,6 +2612,25 @@ Session::add_commands (vector<Command*> const & cmds)
        }
 }
 
+void
+Session::add_command (Command* const cmd)
+{
+       assert (_current_trans);
+       DEBUG_UNDO_HISTORY (
+           string_compose ("Current Undo Transaction %1, adding command: %2",
+                           _current_trans->name (),
+                           cmd->name ()));
+       _current_trans->add_command (cmd);
+}
+
+PBD::StatefulDiffCommand*
+Session::add_stateful_diff_command (boost::shared_ptr<PBD::StatefulDestructible> sfd)
+{
+       PBD::StatefulDiffCommand* cmd = new PBD::StatefulDiffCommand (sfd);
+       add_command (cmd);
+       return cmd;
+}
+
 void
 Session::begin_reversible_command (const string& name)
 {
@@ -2427,10 +2650,17 @@ Session::begin_reversible_command (GQuark q)
        */
 
        if (_current_trans == 0) {
+               DEBUG_UNDO_HISTORY (string_compose (
+                   "Begin Reversible Command, new transaction: %1", g_quark_to_string (q)));
+
                /* start a new transaction */
                assert (_current_trans_quarks.empty ());
                _current_trans = new UndoTransaction();
                _current_trans->set_name (g_quark_to_string (q));
+       } else {
+               DEBUG_UNDO_HISTORY (
+                   string_compose ("Begin Reversible Command, current transaction: %1",
+                                   _current_trans->name ()));
        }
 
        _current_trans_quarks.push_front (q);
@@ -2440,6 +2670,8 @@ void
 Session::abort_reversible_command ()
 {
        if (_current_trans != 0) {
+               DEBUG_UNDO_HISTORY (
+                   string_compose ("Abort Reversible Command: %1", _current_trans->name ()));
                _current_trans->clear();
                delete _current_trans;
                _current_trans = 0;
@@ -2456,18 +2688,34 @@ Session::commit_reversible_command (Command *cmd)
        struct timeval now;
 
        if (cmd) {
+               DEBUG_UNDO_HISTORY (
+                   string_compose ("Current Undo Transaction %1, adding command: %2",
+                                   _current_trans->name (),
+                                   cmd->name ()));
                _current_trans->add_command (cmd);
        }
 
+       DEBUG_UNDO_HISTORY (
+           string_compose ("Commit Reversible Command, current transaction: %1",
+                           _current_trans->name ()));
+
        _current_trans_quarks.pop_front ();
 
        if (!_current_trans_quarks.empty ()) {
+               DEBUG_UNDO_HISTORY (
+                   string_compose ("Commit Reversible Command, transaction is not "
+                                   "top-level, current transaction: %1",
+                                   _current_trans->name ()));
                /* the transaction we're committing is not the top-level one */
                return;
        }
 
        if (_current_trans->empty()) {
                /* no commands were added to the transaction, so just get rid of it */
+               DEBUG_UNDO_HISTORY (
+                   string_compose ("Commit Reversible Command, No commands were "
+                                   "added to current transaction: %1",
+                                   _current_trans->name ()));
                delete _current_trans;
                _current_trans = 0;
                return;
@@ -2635,25 +2883,34 @@ Session::cleanup_regions ()
        bool removed = false;
        const RegionFactory::RegionMap& regions (RegionFactory::regions());
 
-       for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+       for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end();) {
 
                uint32_t used = playlists->region_use_count (i->second);
 
                if (used == 0 && !i->second->automatic ()) {
+                       boost::weak_ptr<Region> w = i->second;
+                       ++i;
                        removed = true;
-                       RegionFactory::map_remove (i->second);
+                       RegionFactory::map_remove (w);
+               } else {
+                       ++i;
                }
        }
 
        if (removed) {
                // re-check to remove parent references of compound regions
-               for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+               for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end();) {
                        if (!(i->second->whole_file() && i->second->max_source_level() > 0)) {
+                               ++i;
                                continue;
                        }
                        assert(boost::dynamic_pointer_cast<PlaylistSource>(i->second->source (0)) != 0);
                        if (0 == playlists->region_use_count (i->second)) {
-                               RegionFactory::map_remove (i->second);
+                               boost::weak_ptr<Region> w = i->second;
+                               ++i;
+                               RegionFactory::map_remove (w);
+                       } else {
+                               ++i;
                        }
                }
        }
@@ -2664,6 +2921,66 @@ Session::cleanup_regions ()
        save_state ("");
 }
 
+bool
+Session::can_cleanup_peakfiles () const
+{
+       if (deletion_in_progress()) {
+               return false;
+       }
+       if (!_writable || (_state_of_the_state & CannotSave)) {
+               warning << _("Cannot cleanup peak-files for read-only session.") << endmsg;
+               return false;
+       }
+        if (record_status() == Recording) {
+               error << _("Cannot cleanup peak-files while recording") << endmsg;
+               return false;
+       }
+       return true;
+}
+
+int
+Session::cleanup_peakfiles ()
+{
+       Glib::Threads::Mutex::Lock lm (peak_cleanup_lock, Glib::Threads::TRY_LOCK);
+       if (!lm.locked()) {
+               return -1;
+       }
+
+       assert (can_cleanup_peakfiles ());
+       assert (!peaks_cleanup_in_progres());
+
+       _state_of_the_state = StateOfTheState (_state_of_the_state | PeakCleanup);
+
+       int timeout = 5000; // 5 seconds
+       while (!SourceFactory::files_with_peaks.empty()) {
+               Glib::usleep (1000);
+               if (--timeout < 0) {
+                       warning << _("Timeout waiting for peak-file creation to terminate before cleanup, please try again later.") << endmsg;
+                       _state_of_the_state = StateOfTheState (_state_of_the_state & (~PeakCleanup));
+                       return -1;
+               }
+       }
+
+       for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+               boost::shared_ptr<AudioSource> as;
+               if ((as = boost::dynamic_pointer_cast<AudioSource> (i->second)) != 0) {
+                       as->close_peakfile();
+               }
+       }
+
+       PBD::clear_directory (session_directory().peak_path());
+
+       _state_of_the_state = StateOfTheState (_state_of_the_state & (~PeakCleanup));
+
+       for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+               boost::shared_ptr<AudioSource> as;
+               if ((as = boost::dynamic_pointer_cast<AudioSource> (i->second)) != 0) {
+                       SourceFactory::setup_peakfile(as, true);
+               }
+       }
+       return 0;
+}
+
 int
 Session::cleanup_sources (CleanupReport& rep)
 {
@@ -2685,6 +3002,17 @@ Session::cleanup_sources (CleanupReport& rep)
 
        _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
 
+       /* this is mostly for windows which doesn't allow file
+        * renaming if the file is in use. But we don't special
+        * case it because we need to know if this causes
+        * problems, and the easiest way to notice that is to
+        * keep it in place for all platforms.
+        */
+
+       request_stop (false);
+       _butler->summon ();
+       _butler->wait_until_finished ();
+
        /* consider deleting all unused playlists */
 
        if (playlists->maybe_delete_unused (boost::bind (Session::ask_about_playlist_deletion, _1))) {
@@ -2758,19 +3086,39 @@ Session::cleanup_sources (CleanupReport& rep)
 
                if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) != 0) {
 
+                       /* this is mostly for windows which doesn't allow file
+                        * renaming if the file is in use. But we don't special
+                        * case it because we need to know if this causes
+                        * problems, and the easiest way to notice that is to
+                        * keep it in place for all platforms.
+                        */
+
+                       fs->close ();
+
                        if (!fs->is_stub()) {
 
                                if (playlists->source_use_count (fs) != 0) {
                                        all_sources.insert (fs->path());
                                } else {
-                                       
+
                                        /* we might not remove this source from disk, because it may be used
                                           by other snapshots, but its not being used in this version
                                           so lets get rid of it now, along with any representative regions
                                           in the region list.
                                        */
-                                       
+
                                        RegionFactory::remove_regions_using_source (i->second);
+
+                                       // also remove source from all_sources
+
+                                       for (set<string>::iterator j = all_sources.begin(); j != all_sources.end(); ++j) {
+                                               spath = Glib::path_get_basename (*j);
+                                               if (spath == i->second->name()) {
+                                                       all_sources.erase (j);
+                                                       break;
+                                               }
+                                       }
+
                                        sources.erase (i);
                                }
                        }
@@ -2803,7 +3151,7 @@ Session::cleanup_sources (CleanupReport& rep)
        /* now try to move all unused files into the "dead" directory(ies) */
 
        for (vector<string>::iterator x = unused.begin(); x != unused.end(); ++x) {
-               struct stat statbuf;
+               GStatBuf statbuf;
 
                string newpath;
 
@@ -2868,7 +3216,7 @@ Session::cleanup_sources (CleanupReport& rep)
 
                }
 
-               stat ((*x).c_str(), &statbuf);
+               g_stat ((*x).c_str(), &statbuf);
 
                if (::rename ((*x).c_str(), newpath.c_str()) != 0) {
                        error << string_compose (_("cannot rename unused file source from %1 to %2 (%3)"),
@@ -2880,12 +3228,12 @@ Session::cleanup_sources (CleanupReport& rep)
                /* see if there an easy to find peakfile for this file, and remove it.
                 */
 
-                string base = basename_nosuffix (*x);
+                string base = Glib::path_get_basename (*x);
                 base += "%A"; /* this is what we add for the channel suffix of all native files,
                                  or for the first channel of embedded files. it will miss
                                  some peakfiles for other channels
                               */
-               string peakpath = peak_path (base);
+               string peakpath = construct_peak_filepath (base);
 
                if (Glib::file_test (peakpath.c_str(), Glib::FILE_TEST_EXISTS)) {
                        if (::g_unlink (peakpath.c_str()) != 0) {
@@ -2899,8 +3247,8 @@ Session::cleanup_sources (CleanupReport& rep)
                }
 
                rep.paths.push_back (*x);
-                rep.space += statbuf.st_size;
-        }
+               rep.space += statbuf.st_size;
+       }
 
        /* dump the history list */
 
@@ -3053,6 +3401,10 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
        case ControllableDescriptor::RemoteControlID:
                r = route_by_remote_id (desc.rid());
                break;
+
+       case ControllableDescriptor::SelectionCount:
+               r = route_by_selected_count (desc.selection_id());
+               break;
        }
 
        if (!r) {
@@ -3064,6 +3416,10 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
                c = r->gain_control ();
                break;
 
+       case ControllableDescriptor::Trim:
+               c = r->trim()->gain_control ();
+               break;
+
        case ControllableDescriptor::Solo:
                 c = r->solo_control();
                break;
@@ -3083,22 +3439,16 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
        }
 
        case ControllableDescriptor::PanDirection:
-        {
-                c = r->pannable()->pan_azimuth_control;
+               c = r->pan_azimuth_control();
                break;
-        }
 
        case ControllableDescriptor::PanWidth:
-        {
-                c = r->pannable()->pan_width_control;
+               c = r->pan_width_control();
                break;
-        }
 
        case ControllableDescriptor::PanElevation:
-        {
-                c = r->pannable()->pan_elevation_control;
+               c = r->pan_elevation_control();
                break;
-        }
 
        case ControllableDescriptor::Balance:
                /* XXX simple pan control */
@@ -3128,26 +3478,12 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
                break;
        }
 
-       case ControllableDescriptor::SendGain:
-       {
+       case ControllableDescriptor::SendGain: {
                uint32_t send = desc.target (0);
-
-               /* revert to zero-based counting */
-
                if (send > 0) {
                        --send;
                }
-
-               boost::shared_ptr<Processor> p = r->nth_send (send);
-
-               if (p) {
-                       boost::shared_ptr<Send> s = boost::dynamic_pointer_cast<Send>(p);
-                       boost::shared_ptr<Amp> a = s->amp();
-                       
-                       if (a) {
-                               c = s->amp()->gain_control();
-                       }
-               }
+               c = r->send_level_controllable (send);
                break;
        }
 
@@ -3186,7 +3522,7 @@ Session::save_history (string snapshot_name)
                return 0;
        }
 
-       if (!Config->get_save_history() || Config->get_saved_history_depth() < 0 || 
+       if (!Config->get_save_history() || Config->get_saved_history_depth() < 0 ||
            (_history.undo_depth() == 0 && _history.redo_depth() == 0)) {
                return 0;
        }
@@ -3448,9 +3784,9 @@ Session::config_changed (std::string p, bool ours)
                }
 
        } else if (p == "click-gain") {
-               
+
                if (_click_gain) {
-                       _click_gain->set_gain (Config->get_click_gain(), this);
+                       _click_gain->gain_control()->set_value (Config->get_click_gain(), Controllable::NoGroup);
                }
 
        } else if (p == "send-mtc") {
@@ -3530,6 +3866,8 @@ Session::config_changed (std::string p, bool ours)
                reconnect_ltc_output ();
        } else if (p == "timecode-generator-offset") {
                ltc_tx_parse_offset();
+       } else if (p == "auto-return-target-list") {
+               follow_playhead_priority ();
        }
 
        set_dirty ();
@@ -3575,7 +3913,20 @@ void
 Session::setup_midi_machine_control ()
 {
        _mmc = new MIDI::MachineControl;
-       _mmc->set_ports (_midi_ports->mmc_input_port(), _midi_ports->mmc_output_port());
+
+       boost::shared_ptr<AsyncMIDIPort> async_in = boost::dynamic_pointer_cast<AsyncMIDIPort> (_midi_ports->mmc_input_port());
+       boost::shared_ptr<AsyncMIDIPort> async_out = boost::dynamic_pointer_cast<AsyncMIDIPort> (_midi_ports->mmc_output_port());
+
+       if (!async_out || !async_out) {
+               return;
+       }
+
+       /* XXXX argh, passing raw pointers back into libmidi++ */
+
+       MIDI::Port* mmc_in = async_in.get();
+       MIDI::Port* mmc_out = async_out.get();
+
+       _mmc->set_ports (mmc_in, mmc_out);
 
        _mmc->Play.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1));
        _mmc->DeferredPlay.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1));
@@ -3613,16 +3964,48 @@ Session::solo_cut_control() const
         return _solo_cut_control;
 }
 
-int
-Session::rename (const std::string& new_name)
+void
+Session::save_snapshot_name (const std::string & n)
 {
-       string legal_name = legalize_for_path (new_name);
-       string newpath;
-       string oldstr;
-       string newstr;
-       bool first = true;
+       /* assure Stateful::_instant_xml is loaded
+        * add_instant_xml() only adds to existing data and defaults
+        * to use an empty Tree otherwise
+        */
+       instant_xml ("LastUsedSnapshot");
 
-       string const old_sources_root = _session_dir->sources_root();
+       XMLNode* last_used_snapshot = new XMLNode ("LastUsedSnapshot");
+       last_used_snapshot->add_property ("name", string(n));
+       add_instant_xml (*last_used_snapshot, false);
+}
+
+void
+Session::set_snapshot_name (const std::string & n)
+{
+       _current_snapshot_name = n;
+       save_snapshot_name (n);
+}
+
+int
+Session::rename (const std::string& new_name)
+{
+       string legal_name = legalize_for_path (new_name);
+       string new_path;
+       string oldstr;
+       string newstr;
+       bool first = true;
+
+       string const old_sources_root = _session_dir->sources_root();
+
+       if (!_writable || (_state_of_the_state & CannotSave)) {
+               error << _("Cannot rename read-only session.") << endmsg;
+               return 0; // don't show "messed up" warning
+       }
+        if (record_status() == Recording) {
+               error << _("Cannot rename session while recording") << endmsg;
+               return 0; // don't show "messed up" warning
+       }
+
+       StateProtector stp (this);
 
        /* Rename:
 
@@ -3630,18 +4013,29 @@ Session::rename (const std::string& new_name)
         * interchange subdirectory
         * session file
         * session history
-        
+
         * Backup files are left unchanged and not renamed.
         */
 
+       /* Windows requires that we close all files before attempting the
+        * rename. This works on other platforms, but isn't necessary there.
+        * Leave it in place for all platforms though, since it may help
+        * catch issues that could arise if the way Source files work ever
+        * change (since most developers are not using Windows).
+        */
+
+       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) {
+                       fs->close ();
+               }
+       }
+
        /* pass one: not 100% safe check that the new directory names don't
         * already exist ...
         */
 
-       vector<space_and_path> new_session_dirs;
-
        for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
-               vector<string> v;
 
                oldstr = (*i).path;
 
@@ -3655,23 +4049,23 @@ Session::rename (const std::string& new_name)
                }
 
                string base = Glib::path_get_dirname (oldstr);
-               string p = Glib::path_get_basename (oldstr);
 
                newstr = Glib::build_filename (base, legal_name);
-               
+
+               cerr << "Looking for " << newstr << endl;
+
                if (Glib::file_test (newstr, Glib::FILE_TEST_EXISTS)) {
+                       cerr << " exists\n";
                        return -1;
                }
-
-               space_and_path sp;
-               sp.path = newstr;
-               sp.blocks = 0; // not needed
-               new_session_dirs.push_back(sp);
        }
 
        /* Session dirs */
 
-       for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+       first = true;
+
+       for (vector<space_and_path>::iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+
                vector<string> v;
 
                oldstr = (*i).path;
@@ -3686,78 +4080,102 @@ Session::rename (const std::string& new_name)
                }
 
                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;                
+               cerr << "for " << oldstr << " new dir = " << newstr << endl;
 
+               cerr << "Rename " << oldstr << " => " << newstr << endl;
                if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
-                       error << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
+                       cerr << string_compose (_("renaming %s as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endl;
+                       error << string_compose (_("renaming %s as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
                        return 1;
                }
 
+               /* Reset path in "session dirs" */
+
+               (*i).path = newstr;
+               (*i).blocks = 0;
+
+               /* reset primary SessionDirectory object */
+
                if (first) {
                        (*_session_dir) = newstr;
-                       newpath = newstr;
-                       first = 1;
+                       new_path = newstr;
+                       first = false;
                }
 
-               /* directory below interchange */
+               /* now rename directory below session_dir/interchange */
+
+               string old_interchange_dir;
+               string new_interchange_dir;
+
+               /* use newstr here because we renamed the path
+                * (folder/directory) that used to be oldstr to newstr above
+                */
 
                v.push_back (newstr);
                v.push_back (interchange_dir_name);
-               v.push_back (p);
+               v.push_back (Glib::path_get_basename (oldstr));
 
-               oldstr = Glib::build_filename (v);
+               old_interchange_dir = 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 (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
-                       error << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
+               new_interchange_dir = Glib::build_filename (v);
+
+               cerr << "Rename " << old_interchange_dir << " => " << new_interchange_dir << endl;
+
+               if (::g_rename (old_interchange_dir.c_str(), new_interchange_dir.c_str()) != 0) {
+                       cerr << string_compose (_("renaming %s as %2 failed (%3)"),
+                                                old_interchange_dir, new_interchange_dir,
+                                                g_strerror (errno))
+                             << endl;
+                       error << string_compose (_("renaming %s as %2 failed (%3)"),
+                                                old_interchange_dir, new_interchange_dir,
+                                                g_strerror (errno))
+                             << endmsg;
                        return 1;
                }
        }
 
-       session_dirs = new_session_dirs;
-
        /* 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;                
+
+       oldstr = Glib::build_filename (new_path, _current_snapshot_name + statefile_suffix);
+       newstr= Glib::build_filename (new_path, legal_name + statefile_suffix);
+
+       cerr << "Rename " << oldstr << " => " << newstr << endl;
 
        if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
+               cerr << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endl;
                error << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
                return 1;
        }
 
        /* history file */
 
-       
-       oldstr = Glib::build_filename (newpath, _current_snapshot_name) + history_suffix;
+       oldstr = Glib::build_filename (new_path, _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;                
-               
+               newstr = Glib::build_filename (new_path, legal_name) + history_suffix;
+
+               cerr << "Rename " << oldstr << " => " << newstr << endl;
+
                if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
+                       cerr << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endl;
                        error << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
                        return 1;
                }
        }
 
+       /* remove old name from recent sessions */
+       remove_recent_sessions (_path);
+       _path = new_path;
+
        /* 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) {
@@ -3768,26 +4186,15 @@ Session::rename (const std::string& new_name)
                }
        }
 
-       /* remove old name from recent sessions */
-
-       remove_recent_sessions (_path);
-
-       _path = newpath;
-       _current_snapshot_name = new_name;
+       set_snapshot_name (new_name);
        _name = new_name;
 
-       /* re-add directory separator - reverse hack to oldstr above */
-       if (_path[_path.length()-1] != G_DIR_SEPARATOR) {
-               _path += G_DIR_SEPARATOR;
-       }
-
        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);
@@ -3823,7 +4230,7 @@ Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFo
        /* sample rate */
 
        const XMLProperty* prop;
-       if ((prop = tree.root()->property (X_("sample-rate"))) != 0) {          
+       if ((prop = tree.root()->property (X_("sample-rate"))) != 0) {
                sample_rate = atoi (prop->value());
                found_sr = true;
        }
@@ -3860,6 +4267,29 @@ Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFo
        return !(found_sr && found_data_format); // zero if they are both found
 }
 
+std::string
+Session::get_snapshot_from_instant (const std::string& session_dir)
+{
+       std::string instant_xml_path = Glib::build_filename (session_dir, "instant.xml");
+
+       if (!Glib::file_test (instant_xml_path, Glib::FILE_TEST_EXISTS)) {
+               return "";
+       }
+
+       XMLTree tree;
+       if (!tree.read (instant_xml_path)) {
+               return "";
+       }
+
+       const XMLProperty* prop;
+       XMLNode *last_used_snapshot = tree.root()->child("LastUsedSnapshot");
+       if (last_used_snapshot && (prop = last_used_snapshot->property ("name")) != 0) {
+               return prop->value();
+       }
+
+       return "";
+}
+
 typedef std::vector<boost::shared_ptr<FileSource> > SeveralFileSources;
 typedef std::map<std::string,SeveralFileSources> SourcePathMap;
 
@@ -3876,21 +4306,20 @@ Session::bring_all_sources_into_session (boost::function<void(uint32_t,uint32_t,
        {
 
                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 {
@@ -3898,40 +4327,45 @@ Session::bring_all_sources_into_session (boost::function<void(uint32_t,uint32_t,
                                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:
+                               /* XXX not implemented yet */
                                break;
                        }
-                       
+
+                       if (new_path.empty()) {
+                               continue;
+                       }
+
                        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.
@@ -3948,3 +4382,443 @@ Session::bring_all_sources_into_session (boost::function<void(uint32_t,uint32_t,
 
        return ret;
 }
+
+static
+bool accept_all_files (string const &, void *)
+{
+       return true;
+}
+
+void
+Session::save_as_bring_callback (uint32_t,uint32_t,string)
+{
+       /* It would be good if this did something useful vis-a-vis save-as, but the arguments doesn't provide the correct information right now to do this.
+       */
+}
+
+static string
+make_new_media_path (string old_path, string new_session_folder, string new_session_path)
+{
+       /* typedir is the "midifiles" or "audiofiles" etc. part of the path. */
+
+       string typedir = Glib::path_get_basename (Glib::path_get_dirname (old_path));
+       vector<string> v;
+       v.push_back (new_session_folder); /* full path */
+       v.push_back (interchange_dir_name);
+       v.push_back (new_session_path);   /* just one directory/folder */
+       v.push_back (typedir);
+       v.push_back (Glib::path_get_basename (old_path));
+
+       return Glib::build_filename (v);
+}
+
+int
+Session::save_as (SaveAs& saveas)
+{
+       vector<string> files;
+       string current_folder = Glib::path_get_dirname (_path);
+       string new_folder = legalize_for_path (saveas.new_name);
+       string to_dir = Glib::build_filename (saveas.new_parent_folder, new_folder);
+       int64_t total_bytes = 0;
+       int64_t copied = 0;
+       int64_t cnt = 0;
+       int64_t all = 0;
+       int32_t internal_file_cnt = 0;
+
+       vector<string> do_not_copy_extensions;
+       do_not_copy_extensions.push_back (statefile_suffix);
+       do_not_copy_extensions.push_back (pending_suffix);
+       do_not_copy_extensions.push_back (backup_suffix);
+       do_not_copy_extensions.push_back (temp_suffix);
+       do_not_copy_extensions.push_back (history_suffix);
+
+       /* get total size */
+
+       for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+
+               /* need to clear this because
+                * find_files_matching_filter() is cumulative
+                */
+
+               files.clear ();
+
+               find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
+
+               all += files.size();
+
+               for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
+                       GStatBuf gsb;
+                       g_stat ((*i).c_str(), &gsb);
+                       total_bytes += gsb.st_size;
+               }
+       }
+
+       /* save old values so we can switch back if we are not switching to the new session */
+
+       string old_path = _path;
+       string old_name = _name;
+       string old_snapshot = _current_snapshot_name;
+       string old_sd = _session_dir->root_path();
+       vector<string> old_search_path[DataType::num_types];
+       string old_config_search_path[DataType::num_types];
+
+       old_search_path[DataType::AUDIO] = source_search_path (DataType::AUDIO);
+       old_search_path[DataType::MIDI] = source_search_path (DataType::MIDI);
+       old_config_search_path[DataType::AUDIO]  = config.get_audio_search_path ();
+       old_config_search_path[DataType::MIDI]  = config.get_midi_search_path ();
+
+       /* switch session directory */
+
+       (*_session_dir) = to_dir;
+
+       /* create new tree */
+
+       if (!_session_dir->create()) {
+               saveas.failure_message = string_compose (_("Cannot create new session folder %1"), to_dir);
+               return -1;
+       }
+
+       try {
+               /* copy all relevant files. Find each location in session_dirs,
+                * and copy files from there to target.
+                */
+
+               for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+
+                       /* need to clear this because
+                        * find_files_matching_filter() is cumulative
+                        */
+
+                       files.clear ();
+
+                       const size_t prefix_len = (*sd).path.size();
+
+                       /* Work just on the files within this session dir */
+
+                       find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
+
+                       /* add dir separator to protect against collisions with
+                        * track names (e.g. track named "audiofiles" or
+                        * "analysis".
+                        */
+
+                       static const std::string audiofile_dir_string = string (sound_dir_name) + G_DIR_SEPARATOR;
+                       static const std::string midifile_dir_string = string (midi_dir_name) + G_DIR_SEPARATOR;
+                       static const std::string analysis_dir_string = analysis_dir() + G_DIR_SEPARATOR;
+
+                       /* copy all the files. Handling is different for media files
+                          than others because of the *silly* subtree we have below the interchange
+                          folder. That really was a bad idea, but I'm not fixing it as part of
+                          implementing ::save_as().
+                       */
+
+                       for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
+
+                               std::string from = *i;
+
+#ifdef __APPLE__
+                               string filename = Glib::path_get_basename (from);
+                               std::transform (filename.begin(), filename.end(), filename.begin(), ::toupper);
+                               if (filename == ".DS_STORE") {
+                                       continue;
+                               }
+#endif
+
+                               if (from.find (audiofile_dir_string) != string::npos) {
+
+                                       /* audio file: only copy if asked */
+
+                                       if (saveas.include_media && saveas.copy_media) {
+
+                                               string to = make_new_media_path (*i, to_dir, new_folder);
+
+                                               info << "media file copying from " << from << " to " << to << endmsg;
+
+                                               if (!copy_file (from, to)) {
+                                                       throw Glib::FileError (Glib::FileError::IO_ERROR,
+                                                                                                  string_compose(_("\ncopying \"%1\" failed !"), from));
+                                               }
+                                       }
+
+                                       /* we found media files inside the session folder */
+
+                                       internal_file_cnt++;
+
+                               } else if (from.find (midifile_dir_string) != string::npos) {
+
+                                       /* midi file: always copy unless
+                                        * creating an empty new session
+                                        */
+
+                                       if (saveas.include_media) {
+
+                                               string to = make_new_media_path (*i, to_dir, new_folder);
+
+                                               info << "media file copying from " << from << " to " << to << endmsg;
+
+                                               if (!copy_file (from, to)) {
+                                                       throw Glib::FileError (Glib::FileError::IO_ERROR, "copy failed");
+                                               }
+                                       }
+
+                                       /* we found media files inside the session folder */
+
+                                       internal_file_cnt++;
+
+                               } else if (from.find (analysis_dir_string) != string::npos) {
+
+                                       /*  make sure analysis dir exists in
+                                        *  new session folder, but we're not
+                                        *  copying analysis files here, see
+                                        *  below
+                                        */
+
+                                       (void) g_mkdir_with_parents (analysis_dir().c_str(), 775);
+                                       continue;
+
+                               } else {
+
+                                       /* normal non-media file. Don't copy state, history, etc.
+                                        */
+
+                                       bool do_copy = true;
+
+                                       for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
+                                               if ((from.length() > (*v).length()) && (from.find (*v) == from.length() - (*v).length())) {
+                                                       /* end of filename matches extension, do not copy file */
+                                                       do_copy = false;
+                                                       break;
+                                               }
+                                       }
+
+                                       if (!saveas.copy_media && from.find (peakfile_suffix) != string::npos) {
+                                               /* don't copy peakfiles if
+                                                * we're not copying media
+                                                */
+                                               do_copy = false;
+                                       }
+
+                                       if (do_copy) {
+                                               string to = Glib::build_filename (to_dir, from.substr (prefix_len));
+
+                                               info << "attempting to make directory/folder " << to << endmsg;
+
+                                               if (g_mkdir_with_parents (Glib::path_get_dirname (to).c_str(), 0755)) {
+                                                       throw Glib::FileError (Glib::FileError::IO_ERROR, "cannot create required directory");
+                                               }
+
+                                               info << "attempting to copy " << from << " to " << to << endmsg;
+
+                                               if (!copy_file (from, to)) {
+                                                       throw Glib::FileError (Glib::FileError::IO_ERROR,
+                                                                                                  string_compose(_("\ncopying \"%1\" failed !"), from));
+                                               }
+                                       }
+                               }
+
+                               /* measure file size even if we're not going to copy so that our Progress
+                                  signals are correct, since we included these do-not-copy files
+                                  in the computation of the total size and file count.
+                               */
+
+                               GStatBuf gsb;
+                               g_stat (from.c_str(), &gsb);
+                               copied += gsb.st_size;
+                               cnt++;
+
+                               double fraction = (double) copied / total_bytes;
+
+                               bool keep_going = true;
+
+                               if (saveas.copy_media) {
+
+                                       /* no need or expectation of this if
+                                        * media is not being copied, because
+                                        * it will be fast(ish).
+                                        */
+
+                                       /* tell someone "X percent, file M of N"; M is one-based */
+
+                                       boost::optional<bool> res = saveas.Progress (fraction, cnt, all);
+
+                                       if (res) {
+                                               keep_going = *res;
+                                       }
+                               }
+
+                               if (!keep_going) {
+                                       throw Glib::FileError (Glib::FileError::FAILED, "copy cancelled");
+                               }
+                       }
+
+               }
+
+               /* copy optional folders, if any */
+
+               string old = plugins_dir ();
+               if (Glib::file_test (old, Glib::FILE_TEST_EXISTS)) {
+                       string newdir = Glib::build_filename (to_dir, Glib::path_get_basename (old));
+                       copy_files (old, newdir);
+               }
+
+               old = externals_dir ();
+               if (Glib::file_test (old, Glib::FILE_TEST_EXISTS)) {
+                       string newdir = Glib::build_filename (to_dir, Glib::path_get_basename (old));
+                       copy_files (old, newdir);
+               }
+
+               old = automation_dir ();
+               if (Glib::file_test (old, Glib::FILE_TEST_EXISTS)) {
+                       string newdir = Glib::build_filename (to_dir, Glib::path_get_basename (old));
+                       copy_files (old, newdir);
+               }
+
+               if (saveas.include_media) {
+
+                       if (saveas.copy_media) {
+#ifndef PLATFORM_WINDOWS
+                               /* There are problems with analysis files on
+                                * Windows, because they used a colon in their
+                                * names as late as 4.0. Colons are not legal
+                                * under Windows even if NTFS allows them.
+                                *
+                                * This is a tricky problem to solve so for
+                                * just don't copy these files. They will be
+                                * regenerated as-needed anyway, subject to the
+                                * existing issue that the filenames will be
+                                * rejected by Windows, which is a separate
+                                * problem (though related).
+                                */
+
+                               /* only needed if we are copying media, since the
+                                * analysis data refers to media data
+                                */
+
+                               old = analysis_dir ();
+                               if (Glib::file_test (old, Glib::FILE_TEST_EXISTS)) {
+                                       string newdir = Glib::build_filename (to_dir, "analysis");
+                                       copy_files (old, newdir);
+                               }
+#endif /* PLATFORM_WINDOWS */
+                       }
+               }
+
+
+               _path = to_dir;
+               set_snapshot_name (saveas.new_name);
+               _name = saveas.new_name;
+
+               if (saveas.include_media && !saveas.copy_media) {
+
+                       /* reset search paths of the new session (which we're pretending to be right now) to
+                          include the original session search path, so we can still find all audio.
+                       */
+
+                       if (internal_file_cnt) {
+                               for (vector<string>::iterator s = old_search_path[DataType::AUDIO].begin(); s != old_search_path[DataType::AUDIO].end(); ++s) {
+                                       ensure_search_path_includes (*s, DataType::AUDIO);
+                                       cerr << "be sure to include " << *s << "  for audio" << endl;
+                               }
+
+                               /* we do not do this for MIDI because we copy
+                                  all MIDI files if saveas.include_media is
+                                  true
+                               */
+                       }
+               }
+
+               bool was_dirty = dirty ();
+
+               save_state ("", false, false, !saveas.include_media);
+               save_default_options ();
+
+               if (saveas.copy_media && saveas.copy_external) {
+                       if (bring_all_sources_into_session (boost::bind (&Session::save_as_bring_callback, this, _1, _2, _3))) {
+                               throw Glib::FileError (Glib::FileError::NO_SPACE_LEFT, "consolidate failed");
+                       }
+               }
+
+               saveas.final_session_folder_name = _path;
+
+               store_recent_sessions (_name, _path);
+
+               if (!saveas.switch_to) {
+
+                       /* switch back to the way things were */
+
+                       _path = old_path;
+                       _name = old_name;
+                       set_snapshot_name (old_snapshot);
+
+                       (*_session_dir) = old_sd;
+
+                       if (was_dirty) {
+                               set_dirty ();
+                       }
+
+                       if (internal_file_cnt) {
+                               /* reset these to their original values */
+                               config.set_audio_search_path (old_config_search_path[DataType::AUDIO]);
+                               config.set_midi_search_path (old_config_search_path[DataType::MIDI]);
+                       }
+
+               } else {
+
+                       /* prune session dirs, and update disk space statistics
+                        */
+
+                       space_and_path sp;
+                       sp.path = _path;
+                       session_dirs.clear ();
+                       session_dirs.push_back (sp);
+                       refresh_disk_space ();
+
+                       /* ensure that all existing tracks reset their current capture source paths
+                        */
+                       reset_write_sources (true, true);
+
+                       /* the copying above was based on actually discovering files, not just iterating over the sources list.
+                          But if we're going to switch to the new (copied) session, we need to change the paths in the sources also.
+                       */
+
+                       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()) {
+                                       string newpath = make_new_media_path (fs->path(), to_dir, new_folder);
+                                       fs->set_path (newpath);
+                               }
+                       }
+               }
+
+       } catch (Glib::FileError& e) {
+
+               saveas.failure_message = e.what();
+
+               /* recursively remove all the directories */
+
+               remove_directory (to_dir);
+
+               /* return error */
+
+               return -1;
+
+       } catch (...) {
+
+               saveas.failure_message = _("unknown reason");
+
+               /* recursively remove all the directories */
+
+               remove_directory (to_dir);
+
+               /* return error */
+
+               return -1;
+       }
+
+       return 0;
+}