output more stuff to the error channel if/when a session fails to load/be created
[ardour.git] / libs / ardour / session_state.cc
index 7fb110da7a6bd36dc7281cad6540a071ff4b2003..d27d9636461ed3afd996e8b0205f85e9041330e3 100644 (file)
@@ -39,7 +39,7 @@
 #include <sys/vfs.h>
 #endif
 
-#ifdef __APPLE__
+#if defined(__APPLE__) || defined(__FreeBSD__)
 #include <sys/param.h>
 #include <sys/mount.h>
 #endif
@@ -66,6 +66,7 @@
 #include "pbd/debug.h"
 #include "pbd/enumwriter.h"
 #include "pbd/error.h"
+#include "pbd/file_archive.h"
 #include "pbd/file_utils.h"
 #include "pbd/pathexpand.h"
 #include "pbd/pthread_utils.h"
@@ -91,6 +92,7 @@
 #include "ardour/filename_extensions.h"
 #include "ardour/graph.h"
 #include "ardour/location.h"
+#include "ardour/lv2_plugin.h"
 #include "ardour/midi_model.h"
 #include "ardour/midi_patch_manager.h"
 #include "ardour/midi_region.h"
 #include "ardour/playlist_source.h"
 #include "ardour/port.h"
 #include "ardour/processor.h"
+#include "ardour/progress.h"
 #include "ardour/profile.h"
 #include "ardour/proxy_controllable.h"
 #include "ardour/recent_sessions.h"
 
 #include "LuaBridge/LuaBridge.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 #include <locale.h>
 
 using namespace std;
@@ -176,7 +179,6 @@ Session::pre_engine_init (string fullpath)
        set_next_event ();
        _all_route_group->set_active (true, this);
        interpolation.add_channel_to (0, 0);
-       _vca_manager = new VCAManager (*this);
 
        if (config.get_use_video_sync()) {
                waiting_for_sync_offset = true;
@@ -235,10 +237,12 @@ Session::post_engine_init ()
        setup_midi_machine_control ();
 
        if (_butler->start_thread()) {
+               error << _("Butler did not start") << endmsg;
                return -1;
        }
 
        if (start_midi_thread ()) {
+               error << _("MIDI I/O thread did not start") << endmsg;
                return -1;
        }
 
@@ -276,6 +280,7 @@ Session::post_engine_init ()
 
                if (state_tree) {
                        if (set_state (*state_tree->root(), Stateful::loading_state_version)) {
+                               error << _("Could not set session state from XML") << endmsg;
                                return -1;
                        }
                } else {
@@ -342,7 +347,11 @@ Session::post_engine_init ()
                /* handle this one in a different way than all others, so that its clear what happened */
                error << err.what() << endmsg;
                return -1;
+       } catch (std::exception const & e) {
+               error << _("Unexpected exception during session setup: ") << e.what() << endmsg;
+               return -1;
        } catch (...) {
+               error << _("Unknown exception during session setup") << endmsg;
                return -1;
        }
 
@@ -1096,6 +1105,8 @@ Session::state (bool full_state)
                }
        }
 
+       node->add_property ("end-is-free", _session_range_end_is_free ? X_("yes") : X_("no"));
+
        /* save the ID counter */
 
        snprintf (buf, sizeof (buf), "%" PRIu64, ID::counter());
@@ -1238,7 +1249,7 @@ 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 the monitor out first */
 
                if (_monitor_out) {
                        assert (_monitor_out == public_order.front());
@@ -1359,6 +1370,10 @@ Session::set_state (const XMLNode& node, int version)
 
        setup_raid_path(_session_dir->root_path());
 
+       if ((prop = node.property (X_("end-is-free"))) != 0) {
+               _session_range_end_is_free = string_is_affirmative (prop->value());
+       }
+
        if ((prop = node.property (X_("id-counter"))) != 0) {
                uint64_t x;
                sscanf (prop->value().c_str(), "%" PRIu64, &x);
@@ -2263,7 +2278,7 @@ Session::save_template (string template_name, bool replace_existing)
 void
 Session::refresh_disk_space ()
 {
-#if __APPLE__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
+#if __APPLE__ || __FreeBSD__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
 
        Glib::Threads::Mutex::Lock lm (space_lock);
 
@@ -2873,14 +2888,17 @@ Session::find_all_sources_across_snapshots (set<string>& result, bool exclude_th
                return 0;
        }
 
-       this_snapshot_path = _path;
-       this_snapshot_path += legalize_for_path (_current_snapshot_name);
+       this_snapshot_path = Glib::build_filename (_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) {
 
+               cerr << "Looking at snapshot " << (*i) << " ( with this = [" << this_snapshot_path << "])\n";
+
                if (exclude_this_snapshot && *i == this_snapshot_path) {
+                       cerr << "\texcluded\n";
                        continue;
+
                }
 
                if (find_all_sources (*i, result) < 0) {
@@ -3011,6 +3029,12 @@ Session::cleanup_peakfiles ()
        return 0;
 }
 
+static void
+merge_all_sources (boost::shared_ptr<const Playlist> pl, std::set<boost::shared_ptr<Source> >* all_sources)
+{
+       pl->deep_sources (*all_sources);
+}
+
 int
 Session::cleanup_sources (CleanupReport& rep)
 {
@@ -3021,14 +3045,14 @@ Session::cleanup_sources (CleanupReport& rep)
        string midi_path;
        vector<string> candidates;
        vector<string> unused;
-       set<string> all_sources;
-       bool used;
+       set<string> sources_used_by_all_snapshots;
        string spath;
        int ret = -1;
        string tmppath1;
        string tmppath2;
        Searchpath asp;
        Searchpath msp;
+       set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
 
        _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
 
@@ -3071,7 +3095,7 @@ Session::cleanup_sources (CleanupReport& rep)
                   capture files.
                */
 
-               if (!i->second->used() && (i->second->length(i->second->timeline_position() > 0))) {
+               if (!i->second->used() && (i->second->length(i->second->timeline_position()) > 0)) {
                        dead_sources.push_back (i->second);
                        i->second->drop_references ();
                }
@@ -3099,12 +3123,21 @@ Session::cleanup_sources (CleanupReport& rep)
        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
-          dropped.
+       /* add sources from all other snapshots as "used", but don't use this
+          snapshot because the state file on disk still references sources we
+          may have already dropped.
        */
 
-       find_all_sources_across_snapshots (all_sources, true);
+       find_all_sources_across_snapshots (sources_used_by_all_snapshots, true);
+
+       /* Although the region factory has a list of all regions ever created
+        * for this session, we're only interested in regions actually in
+        * playlists right now. So merge all playlist regions lists together.
+        *
+        * This will include the playlists used within compound regions.
+        */
+
+       playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot));
 
        /*  add our current source list
         */
@@ -3114,59 +3147,76 @@ Session::cleanup_sources (CleanupReport& rep)
                 SourceMap::iterator tmp = i;
                 ++tmp;
 
-               if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) != 0) {
+               if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) == 0) {
+                       /* not a file */
+                       i = tmp;
+                       continue;
+               }
 
-                       /* 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.
-                        */
+               /* this is mostly for windows which doesn't allow file
+                * renaming if the file is in use. But we do not 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 ();
+               fs->close ();
 
-                       if (!fs->is_stub()) {
+               if (!fs->is_stub()) {
 
-                               if (playlists->source_use_count (fs) != 0) {
-                                       all_sources.insert (fs->path());
-                               } else {
+                       /* Note that we're checking a list of all
+                        * sources across all snapshots with the list
+                        * of sources used by this snapshot.
+                        */
 
-                                       /* 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 (sources_used_by_this_snapshot.find (i->second) != sources_used_by_this_snapshot.end()) {
+                               /* this source is in use by this snapshot */
+                               sources_used_by_all_snapshots.insert (fs->path());
+                               cerr << "Source from source list found in used_by_this_snapshot (" << fs->path() << ")\n";
+                       } else {
+                               cerr << "Source from source list NOT found in used_by_this_snapshot (" << fs->path() << ")\n";
+                               /* this source is NOT in use by this snapshot
+                                */
 
-                                       RegionFactory::remove_regions_using_source (i->second);
+                               /* remove all related regions from RegionFactory master list
+                                */
 
-                                       // also remove source from all_sources
+                               RegionFactory::remove_regions_using_source (i->second);
 
-                                       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;
-                                               }
-                                       }
+                               /* remove from our current source list
+                                * also. We may not remove it from
+                                * disk, because it may be used by
+                                * other snapshots, but it isn't used inside this
+                                * snapshot anymore, so we don't need a
+                                * reference to it.
+                                */
 
-                                       sources.erase (i);
-                               }
+                               sources.erase (i);
                        }
                }
 
                 i = tmp;
        }
 
+       /* now check each candidate source to see if it exists in the list of
+          sources_used_by_all_snapshots. If it doesn't, put it into "unused".
+       */
+
+       cerr << "Candidates: " << candidates.size() << endl;
+       cerr << "Used by others: " << sources_used_by_all_snapshots.size() << endl;
+
        for (vector<string>::iterator x = candidates.begin(); x != candidates.end(); ++x) {
 
-               used = false;
+               bool used = false;
                spath = *x;
 
-               for (set<string>::iterator i = all_sources.begin(); i != all_sources.end(); ++i) {
+               for (set<string>::iterator i = sources_used_by_all_snapshots.begin(); i != sources_used_by_all_snapshots.end(); ++i) {
 
                        tmppath1 = canonical_path (spath);
                        tmppath2 = canonical_path ((*i));
 
+                       cerr << "\t => " << tmppath2 << endl;
+
                        if (tmppath1 == tmppath2) {
                                used = true;
                                break;
@@ -3178,6 +3228,14 @@ Session::cleanup_sources (CleanupReport& rep)
                }
        }
 
+       cerr << "Actually unused: " << unused.size() << endl;
+
+       if (unused.empty()) {
+               /* Nothing to do */
+               ret = 0;
+               goto out;
+       }
+
        /* now try to move all unused files into the "dead" directory(ies) */
 
        for (vector<string>::iterator x = unused.begin(); x != unused.end(); ++x) {
@@ -3240,38 +3298,30 @@ Session::cleanup_sources (CleanupReport& rep)
                                newpath = newpath_v;
                        }
 
-               } else {
-
-                       /* it doesn't exist, or we can't read it or something */
-
                }
 
-               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)"),
-                                         (*x), newpath, strerror (errno))
-                             << endmsg;
-                       goto out;
+               if ((g_stat ((*x).c_str(), &statbuf) != 0) || (::g_rename ((*x).c_str(), newpath.c_str()) != 0)) {
+                       error << string_compose (_("cannot rename unused file source from %1 to %2 (%3)"), (*x),
+                                                newpath, g_strerror (errno)) << endmsg;
+                       continue;
                }
 
                /* see if there an easy to find peakfile for this file, and remove it.
                 */
 
-                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 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 = construct_peak_filepath (base);
 
-               if (Glib::file_test (peakpath.c_str(), Glib::FILE_TEST_EXISTS)) {
-                       if (::g_unlink (peakpath.c_str()) != 0) {
-                               error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
-                                                         peakpath, _path, strerror (errno))
-                                     << endmsg;
+               if (Glib::file_test (peakpath.c_str (), Glib::FILE_TEST_EXISTS)) {
+                       if (::g_unlink (peakpath.c_str ()) != 0) {
+                               error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"), peakpath, _path,
+                                                        g_strerror (errno)) << endmsg;
                                /* try to back out */
-                               ::rename (newpath.c_str(), _path.c_str());
+                               ::g_rename (newpath.c_str (), _path.c_str ());
                                goto out;
                        }
                }
@@ -4874,3 +4924,333 @@ Session::save_as (SaveAs& saveas)
 
        return 0;
 }
+
+static void set_progress (Progress* p, size_t n, size_t t)
+{
+       p->set_progress (float (n) / float(t));
+}
+
+int
+Session::archive_session (const std::string& dest,
+                          const std::string& name,
+                          ArchiveEncode compress_audio,
+                          bool only_used_sources,
+                          Progress* progress)
+{
+       if (dest.empty () || name.empty ()) {
+               return -1;
+       }
+
+       /* save current values */
+       bool was_dirty = dirty ();
+       string old_path = _path;
+       string old_name = _name;
+       string old_snapshot = _current_snapshot_name;
+       string old_sd = _session_dir->root_path();
+       string old_config_search_path[DataType::num_types];
+       old_config_search_path[DataType::AUDIO] = config.get_audio_search_path ();
+       old_config_search_path[DataType::MIDI]  = config.get_midi_search_path ();
+
+       /* ensure that session-path is included in search-path */
+       bool ok = false;
+       for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+               if ((*sd).path == old_path) {
+                       ok = true;
+               }
+       }
+       if (!ok) {
+               return -1;
+       }
+
+       /* create temporary dir to save session to */
+#ifdef PLATFORM_WINDOWS
+       char tmp[256] = "C:\\TEMP\\";
+       GetTempPath (sizeof (tmp), tmp);
+#else
+       char const* tmp = getenv("TMPDIR");
+       if (!tmp) {
+               tmp = "/tmp/";
+       }
+#endif
+       if ((strlen (tmp) + 21) > 1024) {
+               return -1;
+       }
+
+       char tmptpl[1024];
+       strcpy (tmptpl, tmp);
+       strcat (tmptpl, "ardourarchive-XXXXXX");
+       char*  tmpdir = g_mkdtemp (tmptpl);
+
+       if (!tmpdir) {
+               return -1;
+       }
+
+       std::string to_dir = std::string (tmpdir);
+
+       /* switch session directory temporarily */
+       (*_session_dir) = to_dir;
+
+       if (!_session_dir->create()) {
+               (*_session_dir) = old_sd;
+               remove_directory (to_dir);
+               return -1;
+       }
+
+       /* prepare archive */
+       string archive = Glib::build_filename (dest, name + ".tar.xz");
+
+       PBD::ScopedConnectionList progress_connection;
+       PBD::FileArchive ar (archive);
+       if (progress) {
+               ar.progress.connect_same_thread (progress_connection, boost::bind (&set_progress, progress, _1, _2));
+       }
+
+       /* collect files to archive */
+       std::map<string,string> filemap;
+
+       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);
+
+       vector<string> blacklist_dirs;
+       blacklist_dirs.push_back (string (peak_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (analysis_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (dead_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (export_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (externals_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (plugins_dir_name) + G_DIR_SEPARATOR);
+
+       std::map<boost::shared_ptr<AudioFileSource>, std::string> orig_sources;
+
+       set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
+       if (only_used_sources) {
+               playlists->sync_all_regions_with_regions ();
+               playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot), false);
+       }
+
+       // collect audio sources for this session, calc total size for encoding
+       // add option to only include *used* sources (see Session::cleanup_sources)
+       size_t total_size = 0;
+       {
+               Glib::Threads::Mutex::Lock lm (source_lock);
+               for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+                       boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (i->second);
+                       if (!afs || afs->readable_length () == 0) {
+                               continue;
+                       }
+
+                       if (only_used_sources) {
+                               if (!afs->used()) {
+                                       continue;
+                               }
+                               if (sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
+                                       continue;
+                               }
+                       }
+
+                       std::string from = afs->path();
+
+                       if (compress_audio != NO_ENCODE) {
+                               total_size += afs->readable_length ();
+                       } else {
+                               if (afs->within_session()) {
+                                       filemap[from] = make_new_media_path (from, name, name);
+                               } else {
+                                       filemap[from] = make_new_media_path (from, name, name);
+                                       remove_dir_from_search_path (Glib::path_get_dirname (from), DataType::AUDIO);
+                               }
+                       }
+               }
+       }
+
+       /* encode audio */
+       if (compress_audio != NO_ENCODE) {
+               if (progress) {
+                       progress->set_progress (2); // set to "encoding"
+                       progress->set_progress (0);
+               }
+
+               Glib::Threads::Mutex::Lock lm (source_lock);
+               for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+                       boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (i->second);
+                       if (!afs || afs->readable_length () == 0) {
+                               continue;
+                       }
+
+                       if (only_used_sources) {
+                               if (!afs->used()) {
+                                       continue;
+                               }
+                               if (sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
+                                       continue;
+                               }
+                       }
+
+                       orig_sources[afs] = afs->path();
+
+                       std::string new_path = make_new_media_path (afs->path (), to_dir, name);
+                       new_path = Glib::build_filename (Glib::path_get_dirname (new_path), PBD::basename_nosuffix (new_path) + ".flac");
+                       g_mkdir_with_parents (Glib::path_get_dirname (new_path).c_str (), 0755);
+
+                       if (progress) {
+                               progress->descend ((float)afs->readable_length () / total_size);
+                       }
+
+                       try {
+                               SndFileSource* ns = new SndFileSource (*this, *(afs.get()), new_path, compress_audio == FLAC_16BIT, progress);
+                               afs->replace_file (new_path);
+                               delete ns;
+                       } catch (...) {
+                               cerr << "failed to encode " << afs->path() << " to " << new_path << "\n";
+                       }
+
+                       if (progress) {
+                               progress->ascend ();
+                       }
+               }
+       }
+
+       if (progress) {
+               progress->set_progress (-1); // set to "archiving"
+               progress->set_progress (0);
+       }
+
+       /* index files relevant for this session */
+       for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+               vector<string> files;
+
+               size_t prefix_len = (*sd).path.size();
+               if (prefix_len > 0 && (*sd).path.at (prefix_len - 1) != G_DIR_SEPARATOR) {
+                       ++prefix_len;
+               }
+
+               find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
+
+               static const std::string audiofile_dir_string = string (sound_dir_name) + G_DIR_SEPARATOR;
+               static const std::string videofile_dir_string = string (video_dir_name) + G_DIR_SEPARATOR;
+               static const std::string midifile_dir_string  = string (midi_dir_name)  + G_DIR_SEPARATOR;
+
+               for (vector<string>::const_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) {
+                               ; // handled above
+                       } else if (from.find (midifile_dir_string) != string::npos) {
+                               filemap[from] = make_new_media_path (from, name, name);
+                       } else if (from.find (videofile_dir_string) != string::npos) {
+                               filemap[from] = make_new_media_path (from, name, name);
+                       } else {
+                               bool do_copy = true;
+                               for (vector<string>::iterator v = blacklist_dirs.begin(); v != blacklist_dirs.end(); ++v) {
+                                       if (from.find (*v) != string::npos) {
+                                               do_copy = false;
+                                               break;
+                                       }
+                               }
+                               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())) {
+                                               do_copy = false;
+                                               break;
+                                       }
+                               }
+
+                               if (do_copy) {
+                                       filemap[from] = name + G_DIR_SEPARATOR + from.substr (prefix_len);
+                               }
+                       }
+               }
+       }
+
+       /* write session file */
+       _path = to_dir;
+       g_mkdir_with_parents (externals_dir ().c_str (), 0755);
+
+       PBD::Unwinder<bool> uw (LV2Plugin::force_state_save, true);
+       save_state (name);
+       save_default_options ();
+
+       size_t prefix_len = _path.size();
+       if (prefix_len > 0 && _path.at (prefix_len - 1) != G_DIR_SEPARATOR) {
+               ++prefix_len;
+       }
+
+       /* collect session-state files */
+       vector<string> files;
+       do_not_copy_extensions.clear ();
+       do_not_copy_extensions.push_back (history_suffix);
+
+       blacklist_dirs.clear ();
+       blacklist_dirs.push_back (string (externals_dir_name) + G_DIR_SEPARATOR);
+
+       find_files_matching_filter (files, to_dir, accept_all_files, 0, false, true, true);
+       for (vector<string>::const_iterator i = files.begin (); i != files.end (); ++i) {
+               std::string from = *i;
+               bool do_copy = true;
+               for (vector<string>::iterator v = blacklist_dirs.begin(); v != blacklist_dirs.end(); ++v) {
+                       if (from.find (*v) != string::npos) {
+                               do_copy = false;
+                               break;
+                       }
+               }
+               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())) {
+                               do_copy = false;
+                               break;
+                       }
+               }
+               if (do_copy) {
+                       filemap[from] = name + G_DIR_SEPARATOR + from.substr (prefix_len);
+               }
+       }
+
+       /* restore original values */
+       _path = old_path;
+       _name = old_name;
+       set_snapshot_name (old_snapshot);
+       (*_session_dir) = old_sd;
+       if (was_dirty) {
+               set_dirty ();
+       }
+       config.set_audio_search_path (old_config_search_path[DataType::AUDIO]);
+       config.set_midi_search_path (old_config_search_path[DataType::MIDI]);
+
+       for (std::map<boost::shared_ptr<AudioFileSource>, std::string>::iterator i = orig_sources.begin (); i != orig_sources.end (); ++i) {
+               i->first->replace_file (i->second);
+       }
+
+       int rv = ar.create (filemap);
+       remove_directory (to_dir);
+
+       return rv;
+}
+
+void
+Session::undo (uint32_t n)
+{
+       if (actively_recording()) {
+               return;
+       }
+
+       _history.undo (n);
+}
+
+void
+Session::redo (uint32_t n)
+{
+       if (actively_recording()) {
+               return;
+       }
+
+       _history.redo (n);
+}