some more information output to log during save-as, for debugging
[ardour.git] / libs / ardour / session_state.cc
index 5c3ffae3cc087c58031b3384bb5e01e2511ec89d..03acb63f0e0debe3ef368862fe197afe58842ff3 100644 (file)
@@ -54,6 +54,7 @@
 
 #include <glibmm.h>
 #include <glibmm/threads.h>
+#include <glibmm/fileutils.h>
 
 #include <boost/algorithm/string.hpp>
 
 #include "pbd/error.h"
 #include "pbd/file_utils.h"
 #include "pbd/pathexpand.h"
-#include "pbd/pathscanner.h"
 #include "pbd/pthread_utils.h"
 #include "pbd/stacktrace.h"
 #include "pbd/convert.h"
-#include "pbd/clear_dir.h"
 #include "pbd/localtime_r.h"
 
 #include "ardour/amp.h"
+#include "ardour/async_midi_port.h"
 #include "ardour/audio_diskstream.h"
 #include "ardour/audio_track.h"
 #include "ardour/audioengine.h"
 #include "ardour/midi_model.h"
 #include "ardour/midi_patch_manager.h"
 #include "ardour/midi_region.h"
+#include "ardour/midi_scene_changer.h"
 #include "ardour/midi_source.h"
 #include "ardour/midi_track.h"
 #include "ardour/pannable.h"
 #include "ardour/playlist_factory.h"
+#include "ardour/playlist_source.h"
 #include "ardour/port.h"
 #include "ardour/processor.h"
 #include "ardour/proxy_controllable.h"
@@ -137,12 +139,6 @@ Session::pre_engine_init (string fullpath)
        /* discover canonical 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 ? */
 
@@ -207,6 +203,16 @@ 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());
+
+       boost::function<framecnt_t(void)> timer_func (boost::bind (&Session::audible_frame, this));
+       boost::dynamic_pointer_cast<AsyncMIDIPort>(scene_in())->set_timer (timer_func);
+
        setup_midi_machine_control ();
        
        if (_butler->start_thread()) {
@@ -226,6 +232,7 @@ Session::post_engine_init ()
        try {
                /* tempo map requires sample rate knowledge */
 
+               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));
                
@@ -305,8 +312,9 @@ Session::post_engine_init ()
                
                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));
-               _locations->added.connect_same_thread (*this, boost::bind (&Session::locations_added, this, _1));
                
        } catch (AudioEngine::PortRegistrationFailure& err) {
                /* handle this one in a different way than all others, so that its clear what happened */
@@ -321,8 +329,8 @@ Session::post_engine_init ()
        // send_full_time_code (0);
        _engine.transport_locate (0);
 
-       _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset));
-       _mmc->send (MIDI::MachineControlCommand (Timecode::Time ()));
+       send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset));
+       send_immediate_mmc (MIDI::MachineControlCommand (Timecode::Time ()));
 
        MIDI::Name::MidiPatchManager::instance().set_session (this);
 
@@ -500,7 +508,7 @@ Session::create (const string& session_template, BusProfile* bus_profile)
 
                                /* Copy plugin state files from template to new session */
                                std::string template_plugins = Glib::build_filename (session_template, X_("plugins"));
-                               copy_files (template_plugins, plugins_dir ());
+                               copy_recurse (template_plugins, plugins_dir ());
                                
                                return 0;
 
@@ -530,7 +538,7 @@ Session::create (const string& session_template, BusProfile* bus_profile)
                 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));
+                       boost::shared_ptr<Route> r (new Route (*this, _("Master"), Route::MasterOut, DataType::AUDIO));
                         if (r->init ()) {
                                 return -1;
                         }
@@ -650,15 +658,25 @@ 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());
 
+       /* prevent concurrent saves from different threads */
+
+       Glib::Threads::Mutex::Lock lm (save_state_lock);
+
        if (!_writable || (_state_of_the_state & CannotSave)) {
                return 1;
        }
 
+       if (g_atomic_int_get(&_suspend_save)) {
+               _save_queued = true;
+               return 1;
+       }
+       _save_queued = false;
+
        if (!_engine.connected ()) {
                error << string_compose (_("the %1 audio engine is not connected and state saving would lose all I/O connections. Session not saved"),
                                          PROGRAM_NAME)
@@ -676,9 +694,13 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
                }
        }
 
-       SaveSession (); /* EMIT SIGNAL */
+       SessionSaveUnderway (); /* EMIT SIGNAL */
 
-       tree.set_root (&get_state());
+       if (template_only) {
+               tree.set_root (&get_template());
+       } else {
+               tree.set_root (&get_state());
+       }
 
        if (snapshot_name.empty()) {
                snapshot_name = _current_snapshot_name;
@@ -708,8 +730,8 @@ 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;
                if (g_remove (tmp_path.c_str()) != 0) {
@@ -720,6 +742,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;
@@ -798,7 +822,7 @@ Session::load_state (string snapshot_name)
 
        set_dirty();
 
-       _writable = exists_and_writable (xmlpath);
+       _writable = exists_and_writable (xmlpath) && exists_and_writable(Glib::path_get_dirname(xmlpath));
 
        if (!state_tree->read (xmlpath)) {
                error << string_compose(_("Could not understand session file %1"), xmlpath) << endmsg;
@@ -858,11 +882,17 @@ Session::load_state (string snapshot_name)
 int
 Session::load_options (const XMLNode& node)
 {
-       LocaleGuard lg (X_("POSIX"));
+       LocaleGuard lg (X_("C"));
        config.set_variables (node);
        return 0;
 }
 
+bool
+Session::save_default_options ()
+{
+       return config.save_state();
+}
+
 XMLNode&
 Session::get_state()
 {
@@ -916,7 +946,7 @@ Session::state (bool full_state)
                                p += (*i).path;
 
                                if (next != session_dirs.end()) {
-                                       p += ':';
+                                       p += G_SEARCHPATH_SEPARATOR;
                                } else {
                                        break;
                                }
@@ -1117,7 +1147,7 @@ Session::set_state (const XMLNode& node, int version)
 
        if (node.name() != X_("Session")) {
                fatal << _("programming error: Session: incorrect XML node sent to set_state()") << endmsg;
-               return -1;
+               goto out;
        }
 
        if ((prop = node.property ("name")) != 0) {
@@ -1131,7 +1161,7 @@ Session::set_state (const XMLNode& node, int version)
                if (_nominal_frame_rate != _current_frame_rate) {
                         boost::optional<int> r = AskAboutSampleRateMismatch (_nominal_frame_rate, _current_frame_rate);
                        if (r.get_value_or (0)) {
-                               return -1;
+                               goto out;
                        }
                }
        }
@@ -1206,20 +1236,7 @@ Session::set_state (const XMLNode& node, int version)
                goto out;
        }
 
-       Location* location;
-
-       if ((location = _locations->auto_loop_location()) != 0) {
-               set_auto_loop_location (location);
-       }
-
-       if ((location = _locations->auto_punch_location()) != 0) {
-               set_auto_punch_location (location);
-       }
-
-       if ((location = _locations->session_range_location()) != 0) {
-               delete _session_range_location;
-               _session_range_location = location;
-       }
+       locations_changed ();
 
        if (_session_range_location) {
                AudioFileSource::set_header_position_offset (_session_range_location->start());
@@ -1324,9 +1341,13 @@ Session::set_state (const XMLNode& node, int version)
 
        StateReady (); /* EMIT SIGNAL */
 
+       delete state_tree;
+       state_tree = 0;
        return 0;
 
   out:
+       delete state_tree;
+       state_tree = 0;
        return ret;
 }
 
@@ -1360,8 +1381,12 @@ Session::load_routes (const XMLNode& node, int version)
                new_routes.push_back (route);
        }
 
+       BootMessage (_("Tracks/busses loaded;  Adding to Session"));
+
        add_routes (new_routes, false, false, false);
 
+       BootMessage (_("Finished adding tracks/busses"));
+
        return 0;
 }
 
@@ -1409,7 +1434,13 @@ Session::XMLRouteFactory (const XMLNode& node, int version)
                 ret = track;
 
        } else {
-               boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML")));
+               enum Route::Flag flags = Route::Flag(0);
+               const XMLProperty* prop = node.property("flags");
+               if (prop) {
+                       flags = Route::Flag (string_2_enum (prop->value(), flags));
+               }
+
+               boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML"), flags));
 
                 if (r->init () == 0 && r->set_state (node, version) == 0) {
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
@@ -1481,7 +1512,13 @@ Session::XMLRouteFactory_2X (const XMLNode& node, int version)
                 ret = track;
 
        } else {
-               boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML")));
+               enum Route::Flag flags = Route::Flag(0);
+               const XMLProperty* prop = node.property("flags");
+               if (prop) {
+                       flags = Route::Flag (string_2_enum (prop->value(), flags));
+               }
+
+               boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML"), flags));
 
                 if (r->init () == 0 && r->set_state (node, version) == 0) {
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
@@ -1816,47 +1853,31 @@ Session::get_sources_as_xml ()
        return *node;
 }
 
-string
-Session::path_from_region_name (DataType type, string name, string identifier)
+void
+Session::reset_write_sources (bool mark_write_complete, bool force)
 {
-       char buf[PATH_MAX+1];
-       uint32_t n;
-       SessionDirectory sdir(get_best_session_directory_for_new_source());
-       std::string source_dir = ((type == DataType::AUDIO)
-               ? sdir.sound_path() : sdir.midi_path());
-
-        string ext = native_header_format_extension (config.get_native_file_header_format(), type);
-
-       for (n = 0; n < 999999; ++n) {
-               if (identifier.length()) {
-                       snprintf (buf, sizeof(buf), "%s%s%" PRIu32 "%s", name.c_str(),
-                                 identifier.c_str(), n, ext.c_str());
-               } else {
-                       snprintf (buf, sizeof(buf), "%s-%" PRIu32 "%s", name.c_str(),
-                                       n, ext.c_str());
-               }
-
-               std::string source_path = Glib::build_filename (source_dir, buf);
-
-               if (!Glib::file_test (source_path, Glib::FILE_TEST_EXISTS)) {
-                       return source_path;
-               }
-       }
-
-       error << string_compose (_("cannot create new file from region name \"%1\" with ident = \"%2\": too many existing files with similar names"),
-                                name, identifier)
-             << endmsg;
-
-       return "";
+    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) {
+                       
+                       // block state saving
+                       _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)
 {
        XMLNodeList nlist;
        XMLNodeConstIterator niter;
-       boost::shared_ptr<Source> source;
+       boost::shared_ptr<Source> source; /* don't need this but it stops some
+                                          * versions of gcc complaining about
+                                          * discarded return values.
+                                          */
 
        nlist = node.children();
 
@@ -1873,9 +1894,15 @@ Session::load_sources (const XMLNode& node)
 
                         int user_choice;
 
+                       if (err.type == DataType::MIDI && Glib::path_is_absolute (err.path)) {
+                               error << string_compose (_("A external MIDI file is missing. %1 cannot currently recover from missing external MIDI files"),
+                                                        PROGRAM_NAME) << endmsg;
+                               return -1;
+                       }
+
                         if (!no_questions_about_missing_files) {
-                                user_choice = MissingFile (this, err.path, err.type).get_value_or (-1);
-                        } else {
+                               user_choice = MissingFile (this, err.path, err.type).get_value_or (-1);
+                       } else {
                                 user_choice = -2;
                         }
 
@@ -1900,8 +1927,35 @@ Session::load_sources (const XMLNode& node)
 
                         case -1:
                         default:
-                                warning << _("A sound file is missing. It will be replaced by silence.") << endmsg;
-                                source = SourceFactory::createSilent (*this, **niter, max_framecnt, _current_frame_rate);
+                               switch (err.type) {
+
+                               case DataType::AUDIO:
+                                       source = SourceFactory::createSilent (*this, **niter, max_framecnt, _current_frame_rate);
+                                       break;
+
+                               case DataType::MIDI:
+                                       /* The MIDI file is actually missing so
+                                        * just create a new one in the same
+                                        * location. Do not announce its
+                                        */
+                                       string fullpath;
+
+                                       if (!Glib::path_is_absolute (err.path)) {
+                                               fullpath = Glib::build_filename (source_search_path (DataType::MIDI).front(), err.path);
+                                       } else {
+                                               /* this should be an unrecoverable error: we would be creating a MIDI file outside
+                                                  the session tree.
+                                               */
+                                               return -1;
+                                       }
+                                       /* Note that we do not announce the source just yet - we need to reset its ID before we do that */
+                                       source = SourceFactory::createWritable (DataType::MIDI, *this, fullpath, false, _current_frame_rate, false, false);
+                                       /* reset ID to match the missing one */
+                                       source->set_id (**niter);
+                                       /* Now we can announce it */
+                                       SourceFactory::SourceCreated (source);
+                                       break;
+                               }
                                 break;
                         }
                }
@@ -1923,7 +1977,7 @@ Session::XMLSourceFactory (const XMLNode& node)
        }
 
        catch (failed_constructor& err) {
-               error << string_compose (_("Found a sound file that cannot be used by %1. Talk to the progammers."), PROGRAM_NAME) << endmsg;
+               error << string_compose (_("Found a sound file that cannot be used by %1. Talk to the programmers."), PROGRAM_NAME) << endmsg;
                return boost::shared_ptr<Source>();
        }
 }
@@ -1984,7 +2038,7 @@ Session::save_template (string template_name)
                return -1;
        }
 
-       copy_files (plugins_dir(), template_plugin_state_path);
+       copy_recurse (plugins_dir(), template_plugin_state_path);
 
        return 0;
 }
@@ -2035,7 +2089,7 @@ Session::refresh_disk_space ()
                        _total_free_4k_blocks_uncertain = true;
                }
        }
-#elif defined (COMPILER_MSVC)
+#elif defined PLATFORM_WINDOWS
        vector<string> scanned_volumes;
        vector<string>::iterator j;
        vector<space_and_path>::iterator i;
@@ -2087,7 +2141,7 @@ Session::refresh_disk_space ()
 }
 
 string
-Session::get_best_session_directory_for_new_source ()
+Session::get_best_session_directory_for_new_audio ()
 {
        vector<space_and_path>::iterator i;
        string result = _session_dir->root_path();
@@ -2262,12 +2316,6 @@ Session::load_route_groups (const XMLNode& node, int version)
        return 0;
 }
 
-void
-Session::auto_save()
-{
-       save_state (_current_snapshot_name);
-}
-
 static bool
 state_file_filter (const string &str, void* /*arg*/)
 {
@@ -2275,44 +2323,37 @@ state_file_filter (const string &str, void* /*arg*/)
                str.find (statefile_suffix) == (str.length() - strlen (statefile_suffix)));
 }
 
-struct string_cmp {
-       bool operator()(const string* a, const string* b) {
-               return *a < *b;
-       }
-};
-
-static string*
-remove_end(string* state)
+static string
+remove_end(string state)
 {
-       string statename(*state);
+       string statename(state);
 
        string::size_type start,end;
        if ((start = statename.find_last_of (G_DIR_SEPARATOR)) != string::npos) {
                statename = statename.substr (start+1);
        }
 
-       if ((end = statename.rfind(".ardour")) == string::npos) {
+       if ((end = statename.rfind(statefile_suffix)) == string::npos) {
                end = statename.length();
        }
 
-       return new string(statename.substr (0, end));
+       return string(statename.substr (0, end));
 }
 
-vector<string *> *
+vector<string>
 Session::possible_states (string path)
 {
-       PathScanner scanner;
-       vector<string*>* states = scanner (path, state_file_filter, 0, false, false);
+       vector<string> states;
+       find_files_matching_filter (states, path, state_file_filter, 0, false, false);
 
-       transform(states->begin(), states->end(), states->begin(), remove_end);
+       transform(states.begin(), states.end(), states.begin(), remove_end);
 
-       string_cmp cmp;
-       sort (states->begin(), states->end(), cmp);
+       sort (states.begin(), states.end());
 
        return states;
 }
 
-vector<string *> *
+vector<string>
 Session::possible_states () const
 {
        return possible_states(_path);
@@ -2412,6 +2453,17 @@ Session::begin_reversible_command (GQuark q)
        _current_trans_quarks.push_front (q);
 }
 
+void
+Session::abort_reversible_command ()
+{
+       if (_current_trans != 0) {
+               _current_trans->clear();
+               delete _current_trans;
+               _current_trans = 0;
+               _current_trans_quarks.clear();
+       }
+}
+
 void
 Session::commit_reversible_command (Command *cmd)
 {
@@ -2474,11 +2526,16 @@ accept_all_midi_files (const string& path, void* /*arg*/)
 static bool
 accept_all_state_files (const string& path, void* /*arg*/)
 {
-        if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
-                return false;
-        }
+       if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
+               return false;
+       }
 
-       return (path.length() > 7 && path.find (".ardour") == (path.length() - 7));
+       std::string const statefile_ext (statefile_suffix);
+       if (path.length() >= statefile_ext.length()) {
+               return (0 == path.compare (path.length() - statefile_ext.length(), statefile_ext.length(), statefile_ext));
+       } else {
+               return false;
+       }
 }
 
 int
@@ -2536,8 +2593,7 @@ Session::find_all_sources (string path, set<string>& result)
 int
 Session::find_all_sources_across_snapshots (set<string>& result, bool exclude_this_snapshot)
 {
-       PathScanner scanner;
-       vector<string*>* state_files;
+       vector<string> state_files;
        string ripped;
        string this_snapshot_path;
 
@@ -2549,9 +2605,9 @@ Session::find_all_sources_across_snapshots (set<string>& result, bool exclude_th
                ripped = ripped.substr (0, ripped.length() - 1);
        }
 
-       state_files = scanner (ripped, accept_all_state_files, (void *) 0, true, true);
+       find_files_matching_filter (state_files, ripped, accept_all_state_files, (void *) 0, true, true);
 
-       if (state_files == 0) {
+       if (state_files.empty()) {
                /* impossible! */
                return 0;
        }
@@ -2560,13 +2616,13 @@ Session::find_all_sources_across_snapshots (set<string>& result, bool exclude_th
        this_snapshot_path += legalize_for_path (_current_snapshot_name);
        this_snapshot_path += statefile_suffix;
 
-       for (vector<string*>::iterator i = state_files->begin(); i != state_files->end(); ++i) {
+       for (vector<string>::iterator i = state_files.begin(); i != state_files.end(); ++i) {
 
-               if (exclude_this_snapshot && **i == this_snapshot_path) {
+               if (exclude_this_snapshot && *i == this_snapshot_path) {
                        continue;
                }
 
-               if (find_all_sources (**i, result) < 0) {
+               if (find_all_sources (*i, result) < 0) {
                        return -1;
                }
        }
@@ -2593,6 +2649,7 @@ Session::ask_about_playlist_deletion (boost::shared_ptr<Playlist> p)
 void
 Session::cleanup_regions ()
 {
+       bool removed = false;
        const RegionFactory::RegionMap& regions (RegionFactory::regions());
 
        for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
@@ -2600,10 +2657,24 @@ Session::cleanup_regions ()
                uint32_t used = playlists->region_use_count (i->second);
 
                if (used == 0 && !i->second->automatic ()) {
+                       removed = true;
                        RegionFactory::map_remove (i->second);
                }
        }
 
+       if (removed) {
+               // re-check to remove parent references of compound regions
+               for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+                       if (!(i->second->whole_file() && i->second->max_source_level() > 0)) {
+                               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);
+                       }
+               }
+       }
+
        /* dump the history list */
        _history.clear ();
 
@@ -2616,13 +2687,9 @@ Session::cleanup_sources (CleanupReport& rep)
        // FIXME: needs adaptation to midi
 
        vector<boost::shared_ptr<Source> > dead_sources;
-       PathScanner scanner;
        string audio_path;
        string midi_path;
-       vector<space_and_path>::iterator i;
-       vector<space_and_path>::iterator nexti;
-       vector<string*>* candidates;
-       vector<string*>* candidates2;
+       vector<string> candidates;
        vector<string> unused;
        set<string> all_sources;
        bool used;
@@ -2630,6 +2697,8 @@ Session::cleanup_sources (CleanupReport& rep)
        int ret = -1;
        string tmppath1;
        string tmppath2;
+       Searchpath asp;
+       Searchpath msp;
 
        _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
 
@@ -2671,54 +2740,23 @@ Session::cleanup_sources (CleanupReport& rep)
 
        /* build a list of all the possible audio directories for the session */
 
-       for (i = session_dirs.begin(); i != session_dirs.end(); ) {
-
-               nexti = i;
-               ++nexti;
-
+       for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
                SessionDirectory sdir ((*i).path);
-               audio_path += sdir.sound_path();
-
-               if (nexti != session_dirs.end()) {
-                       audio_path += ':';
-               }
-
-               i = nexti;
+               asp += sdir.sound_path();
        }
+       audio_path += asp.to_string();
 
 
        /* build a list of all the possible midi directories for the session */
 
-       for (i = session_dirs.begin(); i != session_dirs.end(); ) {
-
-               nexti = i;
-               ++nexti;
-
+       for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
                SessionDirectory sdir ((*i).path);
-               midi_path += sdir.midi_path();
-
-               if (nexti != session_dirs.end()) {
-                       midi_path += ':';
-               }
-
-               i = nexti;
+               msp += sdir.midi_path();
        }
+       midi_path += msp.to_string();
 
-       candidates = scanner (audio_path, accept_all_audio_files, (void *) 0, true, true);
-       candidates2 = scanner (midi_path, accept_all_midi_files, (void *) 0, true, true);
-
-        /* merge them */
-
-        if (candidates) {
-                if (candidates2) {
-                        for (vector<string*>::iterator i = candidates2->begin(); i != candidates2->end(); ++i) {
-                                candidates->push_back (*i);
-                        }
-                        delete candidates2;
-                }
-        } else {
-                candidates = candidates2; // might still be null
-        }
+       find_files_matching_filter (candidates, audio_path, accept_all_audio_files, (void *) 0, true, true);
+       find_files_matching_filter (candidates, midi_path, accept_all_midi_files, (void *) 0, true, true);
 
        /* find all sources, but don't use this snapshot because the
           state file on disk still references sources we may have already
@@ -2736,50 +2774,58 @@ Session::cleanup_sources (CleanupReport& rep)
                 ++tmp;
 
                if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) != 0) {
-                        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.
-                                */
+                       if (!fs->is_stub()) {
 
-                                RegionFactory::remove_regions_using_source (i->second);
-                                sources.erase (i);
-                        }
+                               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);
+                                       sources.erase (i);
+                                       
+                                       // 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;
+                                               }
+                                       }
+                               }
+                       }
                }
 
                 i = tmp;
        }
 
-        if (candidates) {
-                for (vector<string*>::iterator x = candidates->begin(); x != candidates->end(); ++x) {
-
-                        used = false;
-                        spath = **x;
-
-                        for (set<string>::iterator i = all_sources.begin(); i != all_sources.end(); ++i) {
+       for (vector<string>::iterator x = candidates.begin(); x != candidates.end(); ++x) {
 
-                               tmppath1 = canonical_path (spath);
-                               tmppath2 = canonical_path ((*i));
+               used = false;
+               spath = *x;
 
-                               if (tmppath1 == tmppath2) {
-                                        used = true;
-                                        break;
-                                }
-                        }
+               for (set<string>::iterator i = all_sources.begin(); i != all_sources.end(); ++i) {
 
-                        if (!used) {
-                                unused.push_back (spath);
-                        }
+                       tmppath1 = canonical_path (spath);
+                       tmppath2 = canonical_path ((*i));
 
-                        delete *x;
-                }
+                       if (tmppath1 == tmppath2) {
+                               used = true;
+                               break;
+                       }
+               }
 
-                delete candidates;
-        }
+               if (!used) {
+                       unused.push_back (spath);
+               }
+       }
 
        /* now try to move all unused files into the "dead" directory(ies) */
 
@@ -2924,6 +2970,12 @@ Session::cleanup_trash_sources (CleanupReport& rep)
 void
 Session::set_dirty ()
 {
+       /* never mark session dirty during loading */
+
+       if (_state_of_the_state & Loading) {
+               return;
+       }
+
        bool was_dirty = dirty();
 
        _state_of_the_state = StateOfTheState (_state_of_the_state | Dirty);
@@ -3015,7 +3067,7 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
        case ControllableDescriptor::NamedRoute:
        {
                std::string str = desc.top_level_name();
-               if (str == "master") {
+               if (str == "Master" || str == "master") {
                        r = _master_out;
                } else if (str == "control" || str == "listen") {
                        r = _monitor_out;
@@ -3039,6 +3091,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;
@@ -3161,6 +3217,11 @@ Session::save_history (string snapshot_name)
                return 0;
        }
 
+       if (!Config->get_save_history() || Config->get_saved_history_depth() < 0 || 
+           (_history.undo_depth() == 0 && _history.redo_depth() == 0)) {
+               return 0;
+       }
+
        if (snapshot_name.empty()) {
                snapshot_name = _current_snapshot_name;
        }
@@ -3177,10 +3238,6 @@ Session::save_history (string snapshot_name)
                }
        }
 
-       if (!Config->get_save_history() || Config->get_saved_history_depth() < 0) {
-               return 0;
-       }
-
        tree.set_root (&_history.get_state (Config->get_saved_history_depth()));
 
        if (!tree.write (xml_path))
@@ -3492,12 +3549,12 @@ Session::config_changed (std::string p, bool ours)
                listen_position_changed ();
        } else if (p == "solo-control-is-listen-control") {
                solo_control_mode_changed ();
+       } else if (p == "solo-mute-gain") {
+               _solo_cut_control->Changed();
        } else if (p == "timecode-offset" || p == "timecode-offset-negative") {
                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());
        } else if (p == "ltc-source-port") {
                reconnect_ltc_input ();
        } else if (p == "ltc-sink-port") {
@@ -3591,14 +3648,23 @@ int
 Session::rename (const std::string& new_name)
 {
        string legal_name = legalize_for_path (new_name);
-       string newpath;
+       string new_path;
        string oldstr;
        string newstr;
        bool first = true;
 
        string const old_sources_root = _session_dir->sources_root();
 
-#define RENAME ::rename
+       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:
 
@@ -3615,110 +3681,144 @@ Session::rename (const std::string& new_name)
         */
 
        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 << "Looking for " << newstr << endl;
+               
                if (Glib::file_test (newstr, Glib::FILE_TEST_EXISTS)) {
+                       cerr << " exists\n";
                        return -1;
                }
        }
 
        /* Session dirs */
+
+       first = true;
        
-       for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+       for (vector<space_and_path>::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 << "for " << oldstr << " new dir = " << newstr << endl;
+               
                cerr << "Rename " << oldstr << " => " << newstr << endl;                
-
-               if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+               if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
+                       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 */
 
-               v.push_back (newstr);
+               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;
+               new_interchange_dir = Glib::build_filename (v);
+               
+               cerr << "Rename " << old_interchange_dir << " => " << new_interchange_dir << endl;
                
-               if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+               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;
                }
        }
 
        /* state file */
        
-       oldstr = Glib::build_filename (newpath, _current_snapshot_name) + statefile_suffix;
-       newstr= Glib::build_filename (newpath, legal_name) + statefile_suffix;
+       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 (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+       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;
+               newstr = Glib::build_filename (new_path, legal_name) + history_suffix;
                
                cerr << "Rename " << oldstr << " => " << newstr << endl;                
                
-               if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+               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) {
@@ -3727,31 +3827,24 @@ Session::rename (const std::string& new_name)
                        string p = fs->path ();
                        boost::replace_all (p, old_sources_root, _session_dir->sources_root());
                        fs->set_path (p);
+                       SourceFactory::setup_peakfile(i->second, true);
                }
        }
 
-       /* 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
 }
 
 int
@@ -3818,3 +3911,452 @@ Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFo
 
        return !(found_sr && found_data_format); // zero if they are both found
 }
+
+typedef std::vector<boost::shared_ptr<FileSource> > SeveralFileSources;
+typedef std::map<std::string,SeveralFileSources> SourcePathMap;
+
+int
+Session::bring_all_sources_into_session (boost::function<void(uint32_t,uint32_t,string)> callback)
+{
+       uint32_t total = 0;
+       uint32_t n = 0;
+       SourcePathMap source_path_map;
+       string new_path;
+       boost::shared_ptr<AudioFileSource> afs;
+       int ret = 0;
+
+       {
+
+               Glib::Threads::Mutex::Lock lm (source_lock);
+               
+               cerr << " total sources = " << sources.size();
+               
+               for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+                       boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
+                       
+                       if (!fs) {
+                               continue;
+                       }
+                       
+                       if (fs->within_session()) {
+                               continue;
+                       }
+                       
+                       if (source_path_map.find (fs->path()) != source_path_map.end()) {
+                               source_path_map[fs->path()].push_back (fs);
+                       } else {
+                               SeveralFileSources v;
+                               v.push_back (fs);
+                               source_path_map.insert (make_pair (fs->path(), v));
+                       }
+                       
+                       total++;
+               }
+               
+               cerr << " fsources = " << total << endl;
+               
+               for (SourcePathMap::iterator i = source_path_map.begin(); i != source_path_map.end(); ++i) {
+                       
+                       /* tell caller where we are */
+                       
+                       string old_path = i->first;
+                       
+                       callback (n, total, old_path);
+                       
+                       cerr << old_path << endl;
+                       
+                       new_path.clear ();
+                       
+                       switch (i->second.front()->type()) {
+                       case DataType::AUDIO:
+                               new_path = new_audio_source_path_for_embedded (old_path);
+                               break;
+                               
+                       case DataType::MIDI:
+                               /* 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.
+                       */
+                       remove_dir_from_search_path (Glib::path_get_dirname (old_path), i->second.front()->type());
+
+                       for (SeveralFileSources::iterator f = i->second.begin(); f != i->second.end(); ++f) {
+                               (*f)->set_path (new_path);
+                       }
+               }
+       }
+
+       save_state ("", false, false);
+
+       return ret;
+}
+
+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);
+                       
+                       /* 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;
+                               
+                               if ((*i).find (interchange_dir_name) != string::npos) {
+                                       
+                                       /* media file */
+
+                                       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, "copy failed");
+                                               }
+                                       }
+                                       
+                                       /* we found media files inside the session folder */
+                                       
+                                       internal_file_cnt++;
+                                       
+                               } 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 (((*i).length() > (*v).length()) && ((*i).find (*v) == (*i).length() - (*v).length())) {
+                                                       /* end of filename matches extension, do not copy file */
+                                                       do_copy = false;
+                                                       break;
+                                               } 
+                                       }
+                                       
+                                       if (do_copy) {
+                                               string to = Glib::build_filename (to_dir, (*i).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, "copy failed");
+                                               }
+                                       }
+                               }
+                               
+                               /* 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 ((*i).c_str(), &gsb);
+                               copied += gsb.st_size;
+                               cnt++;
+                               
+                               double fraction = (double) copied / total_bytes;
+                               
+                               /* tell someone "X percent, file M of N"; M is one-based */
+                               
+                               boost::optional<bool> res = saveas.Progress (fraction, cnt, all);
+                               bool keep_going = true;
+
+                               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) {
+                               
+                               /* 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);
+                               }
+                       }
+               }
+                       
+               
+               _path = to_dir;
+               _current_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;
+                               }
+
+                               for (vector<string>::iterator s = old_search_path[DataType::MIDI].begin(); s != old_search_path[DataType::MIDI].end(); ++s) {
+                                       ensure_search_path_includes (*s, DataType::MIDI);
+                               }
+                       }
+               }
+               
+               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;
+               
+               if (!saveas.switch_to) {
+
+                       /* switch back to the way things were */
+
+                       _path = old_path;
+                       _name = old_name;
+                       _current_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;
+}