X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession_state.cc;h=62edaa39115fe8e5b9b556228c4f283d341cd844;hb=453ed61c4a23551c7ffeee5d972b29d6d92c1591;hp=7d6f44b7861d2566c1974b48c7e4cc76ca5ad7f0;hpb=0082e3364f7682ff41df52305cfff2cf7a861ef3;p=ardour.git diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 7d6f44b786..62edaa3911 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -33,23 +33,22 @@ #include #include #include -#include -#include #include -#include #include #ifdef HAVE_SYS_VFS_H #include -#else -#include -#include +#endif + +#ifdef HAVE_SYS_STATVFS_H +#include #endif #include +#include #include -#include +#include #include @@ -57,49 +56,43 @@ #include "midi++/port.h" #include "midi++/manager.h" +#include "evoral/SMF.hpp" + #include "pbd/boost_debug.h" #include "pbd/basename.h" #include "pbd/controllable_descriptor.h" #include "pbd/enumwriter.h" #include "pbd/error.h" +#include "pbd/file_utils.h" +#include "pbd/pathexpand.h" #include "pbd/pathscanner.h" #include "pbd/pthread_utils.h" -#include "pbd/search_path.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/audio_diskstream.h" -#include "ardour/audio_playlist_source.h" #include "ardour/audio_track.h" #include "ardour/audioengine.h" #include "ardour/audiofilesource.h" -#include "ardour/audioplaylist.h" #include "ardour/audioregion.h" -#include "ardour/auditioner.h" #include "ardour/automation_control.h" -#include "ardour/buffer.h" #include "ardour/butler.h" -#include "ardour/configuration.h" #include "ardour/control_protocol_manager.h" -#include "ardour/crossfade.h" -#include "ardour/cycle_timer.h" #include "ardour/directory_names.h" #include "ardour/filename_extensions.h" -#include "ardour/io_processor.h" #include "ardour/location.h" -#include "ardour/midi_diskstream.h" #include "ardour/midi_model.h" #include "ardour/midi_patch_manager.h" -#include "ardour/midi_playlist.h" #include "ardour/midi_region.h" #include "ardour/midi_source.h" #include "ardour/midi_track.h" -#include "ardour/named_selection.h" #include "ardour/pannable.h" -#include "ardour/processor.h" +#include "ardour/playlist_factory.h" #include "ardour/port.h" +#include "ardour/processor.h" #include "ardour/proxy_controllable.h" #include "ardour/recent_sessions.h" #include "ardour/region_factory.h" @@ -108,13 +101,9 @@ #include "ardour/session.h" #include "ardour/session_directory.h" #include "ardour/session_metadata.h" -#include "ardour/session_state_utils.h" #include "ardour/session_playlists.h" -#include "ardour/session_utils.h" +#include "ardour/session_state_utils.h" #include "ardour/silentfilesource.h" -#include "ardour/slave.h" -#include "ardour/smf_source.h" -#include "ardour/sndfile_helpers.h" #include "ardour/sndfilesource.h" #include "ardour/source_factory.h" #include "ardour/speakers.h" @@ -122,10 +111,6 @@ #include "ardour/tempo.h" #include "ardour/ticker.h" #include "ardour/user_bundle.h" -#include "ardour/utils.h" -#include "ardour/utils.h" -#include "ardour/version.h" -#include "ardour/playlist_factory.h" #include "control_protocol/control_protocol.h" @@ -145,14 +130,7 @@ Session::first_stage_init (string fullpath, string snapshot_name) throw failed_constructor(); } - char buf[PATH_MAX+1]; - if (!realpath (fullpath.c_str(), buf) && (errno != ENOENT)) { - error << string_compose(_("Could not use path %1 (%s)"), buf, strerror(errno)) << endmsg; - destroy (); - throw failed_constructor(); - } - - _path = string(buf); + _path = canonical_path (fullpath); if (_path[_path.length()-1] != G_DIR_SEPARATOR) { _path += G_DIR_SEPARATOR; @@ -179,6 +157,7 @@ Session::first_stage_init (string fullpath, string snapshot_name) _solo_isolated_cnt = 0; g_atomic_int_set (&processing_prohibited, 0); _transport_speed = 0; + _default_transport_speed = 1.0; _last_transport_speed = 0; _target_transport_speed = 0; auto_play_legal = false; @@ -216,6 +195,7 @@ Session::first_stage_init (string fullpath, string snapshot_name) _play_range = false; _exporting = false; pending_abort = false; + _adding_routes_in_progress = false; destructive_index = 0; first_file_data_format_reset = true; first_file_header_format_reset = true; @@ -224,12 +204,14 @@ Session::first_stage_init (string fullpath, string snapshot_name) _step_editors = 0; no_questions_about_missing_files = false; _speakers.reset (new Speakers); + _clicks_cleared = 0; + ignore_route_processor_changes = false; + _pre_export_mmc_enabled = false; AudioDiskstream::allocate_working_buffers(); /* default short fade = 15ms */ - Crossfade::set_short_xfade_length ((framecnt_t) floor (config.get_short_xfade_seconds() * frame_rate())); SndFileSource::setup_standard_crossfades (*this, frame_rate()); last_mmc_step.tv_sec = 0; @@ -295,7 +277,7 @@ Session::first_stage_init (string fullpath, string snapshot_name) int Session::second_stage_init () { - AudioFileSource::set_peak_dir (_session_dir->peak_path().to_string()); + AudioFileSource::set_peak_dir (_session_dir->peak_path()); if (!_is_new) { if (load_state (_current_snapshot_name)) { @@ -346,6 +328,9 @@ Session::second_stage_init () _engine.Halted.connect_same_thread (*this, boost::bind (&Session::engine_halted, this)); _engine.Xrun.connect_same_thread (*this, boost::bind (&Session::xrun_recovery, this)); + midi_clock = new MidiClockTicker (); + midi_clock->set_session (this); + try { when_engine_running (); } @@ -369,9 +354,9 @@ Session::second_stage_init () MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset)); MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (Timecode::Time ())); - MidiClockTicker::instance().set_session (this); MIDI::Name::MidiPatchManager::instance().set_session (this); + ltc_tx_initialize(); /* initial program change will be delivered later; see ::config_changed() */ _state_of_the_state = Clean; @@ -397,7 +382,7 @@ Session::raid_path () const SearchPath raid_search_path; for (vector::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) { - raid_search_path += sys::path((*i).path); + raid_search_path += (*i).path; } return raid_search_path.to_string (); @@ -420,7 +405,7 @@ Session::setup_raid_path (string path) SearchPath midi_search_path; for (SearchPath::const_iterator i = search_path.begin(); i != search_path.end(); ++i) { - sp.path = (*i).to_string (); + sp.path = *i; sp.blocks = 0; // not needed session_dirs.push_back (sp); @@ -438,7 +423,7 @@ bool Session::path_is_within_session (const std::string& path) { for (vector::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) { - if (path.find ((*i).path) == 0) { + if (PBD::path_is_within (i->path, path)) { return true; } } @@ -450,35 +435,35 @@ Session::ensure_subdirs () { string dir; - dir = session_directory().peak_path().to_string(); + dir = session_directory().peak_path(); if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { error << string_compose(_("Session: cannot create session peakfile folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; return -1; } - dir = session_directory().sound_path().to_string(); + dir = session_directory().sound_path(); if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { error << string_compose(_("Session: cannot create session sounds dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg; return -1; } - dir = session_directory().midi_path().to_string(); + dir = session_directory().midi_path(); if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { error << string_compose(_("Session: cannot create session midi dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg; return -1; } - dir = session_directory().dead_path().to_string(); + dir = session_directory().dead_path(); if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { error << string_compose(_("Session: cannot create session dead sounds folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; return -1; } - dir = session_directory().export_path().to_string(); + dir = session_directory().export_path(); if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { error << string_compose(_("Session: cannot create session export folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; @@ -499,6 +484,13 @@ Session::ensure_subdirs () return -1; } + dir = externals_dir (); + + if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { + error << string_compose(_("Session: cannot create session externals folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + return -1; + } + return 0; } @@ -517,7 +509,7 @@ Session::create (const string& session_template, BusProfile* bus_profile) return -1; } - _writable = exists_and_writable (sys::path (_path)); + _writable = exists_and_writable (_path); if (!session_template.empty()) { std::string in_path = session_template_dir_to_file (session_template); @@ -536,9 +528,8 @@ Session::create (const string& session_template, BusProfile* bus_profile) _is_new = false; /* Copy plugin state files from template to new session */ - sys::path template_plugins = session_template; - template_plugins /= X_("plugins"); - sys::copy_files (template_plugins, plugins_dir ()); + std::string template_plugins = Glib::build_filename (session_template, X_("plugins")); + copy_files (template_plugins, plugins_dir ()); return 0; @@ -556,10 +547,6 @@ Session::create (const string& session_template, BusProfile* bus_profile) } - /* Instantiate metadata */ - - _metadata = new SessionMetadata (); - /* set initial start + end point */ _state_of_the_state = Clean; @@ -569,7 +556,6 @@ Session::create (const string& session_template, BusProfile* bus_profile) if (bus_profile) { RouteList rl; - int control_id = 1; ChanCount count(DataType::AUDIO, bus_profile->master_out_channels); if (bus_profile->master_out_channels) { @@ -581,39 +567,20 @@ Session::create (const string& session_template, BusProfile* bus_profile) // boost_debug_shared_ptr_mark_interesting (r.get(), "Route"); #endif { - Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); r->input()->ensure_io (count, false, this); r->output()->ensure_io (count, false, this); } - r->set_remote_control_id (control_id++); rl.push_back (r); - if (Config->get_use_monitor_bus()) { - boost::shared_ptr r (new Route (*this, _("monitor"), Route::MonitorOut, DataType::AUDIO)); - if (r->init ()) { - return -1; - } -#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - // boost_debug_shared_ptr_mark_interesting (r.get(), "Route"); -#endif - { - Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - r->input()->ensure_io (count, false, this); - r->output()->ensure_io (count, false, this); - } - r->set_remote_control_id (control_id); - - rl.push_back (r); - } - } else { /* prohibit auto-connect to master, because there isn't one */ bus_profile->output_ac = AutoConnectOption (bus_profile->output_ac & ~AutoConnectMaster); } if (!rl.empty()) { - add_routes (rl, false, false); + add_routes (rl, false, false, false); } /* this allows the user to override settings with an environment variable. @@ -628,6 +595,10 @@ Session::create (const string& session_template, BusProfile* bus_profile) Config->set_output_auto_connect (bus_profile->output_ac); } + if (Config->get_use_monitor_bus() && bus_profile) { + add_monitor_section (); + } + save_state (""); return 0; @@ -644,18 +615,15 @@ Session::maybe_write_autosave() void Session::remove_pending_capture_state () { - sys::path pending_state_file_path(_session_dir->root_path()); + std::string pending_state_file_path(_session_dir->root_path()); - pending_state_file_path /= legalize_for_path (_current_snapshot_name) + pending_suffix; + pending_state_file_path = Glib::build_filename (pending_state_file_path, legalize_for_path (_current_snapshot_name) + pending_suffix); - try - { - sys::remove (pending_state_file_path); - } - catch(sys::filesystem_error& ex) - { - error << string_compose(_("Could remove pending capture state at path \"%1\" (%2)"), - pending_state_file_path.to_string(), ex.what()) << endmsg; + if (!Glib::file_test (pending_state_file_path, Glib::FILE_TEST_EXISTS)) return; + + if (g_remove (pending_state_file_path.c_str()) != 0) { + error << string_compose(_("Could not remove pending capture state at path \"%1\" (%2)"), + pending_state_file_path, g_strerror (errno)) << endmsg; } } @@ -674,17 +642,12 @@ Session::rename_state (string old_name, string new_name) const string old_xml_filename = legalize_for_path (old_name) + statefile_suffix; const string new_xml_filename = legalize_for_path (new_name) + statefile_suffix; - const sys::path old_xml_path = _session_dir->root_path() / old_xml_filename; - const sys::path new_xml_path = _session_dir->root_path() / new_xml_filename; + const std::string old_xml_path(Glib::build_filename (_session_dir->root_path(), old_xml_filename)); + const std::string new_xml_path(Glib::build_filename (_session_dir->root_path(), new_xml_filename)); - try - { - sys::rename (old_xml_path, new_xml_path); - } - catch (const sys::filesystem_error& err) - { + if (::g_rename (old_xml_path.c_str(), new_xml_path.c_str()) != 0) { error << string_compose(_("could not rename snapshot %1 to %2 (%3)"), - old_name, new_name, err.what()) << endmsg; + old_name, new_name, g_strerror(errno)) << endmsg; } } @@ -694,14 +657,14 @@ Session::rename_state (string old_name, string new_name) void Session::remove_state (string snapshot_name) { - if (snapshot_name == _current_snapshot_name || snapshot_name == _name) { + if (!_writable || snapshot_name == _current_snapshot_name || snapshot_name == _name) { // refuse to remove the current snapshot or the "main" one return; } - sys::path xml_path(_session_dir->root_path()); + std::string xml_path(_session_dir->root_path()); - xml_path /= legalize_for_path (snapshot_name) + statefile_suffix; + xml_path = Glib::build_filename (xml_path, legalize_for_path (snapshot_name) + statefile_suffix); if (!create_backup_file (xml_path)) { // don't remove it if a backup can't be made @@ -710,14 +673,17 @@ Session::remove_state (string snapshot_name) } // and delete it - sys::remove (xml_path); + if (g_remove (xml_path.c_str()) != 0) { + error << string_compose(_("Could not remove session file at path \"%1\" (%2)"), + xml_path, g_strerror (errno)) << endmsg; + } } #ifdef HAVE_JACK_SESSION void Session::jack_session_event (jack_session_event_t * event) { - char timebuf[128]; + char timebuf[128], *tmp; time_t n; struct tm local_time; @@ -725,6 +691,8 @@ Session::jack_session_event (jack_session_event_t * event) localtime_r (&n, &local_time); strftime (timebuf, sizeof(timebuf), "JS_%FT%T", &local_time); + while ((tmp = strchr(timebuf, ':'))) { *tmp = '.'; } + if (event->type == JackSessionSaveTemplate) { if (save_template( timebuf )) { @@ -743,13 +711,14 @@ Session::jack_session_event (jack_session_event_t * event) if (save_state (timebuf)) { event->flags = JackSessionSaveError; } else { - sys::path xml_path (_session_dir->root_path()); - xml_path /= legalize_for_path (timebuf) + statefile_suffix; + std::string xml_path (_session_dir->root_path()); + std::string legalized_filename = legalize_for_path (timebuf) + statefile_suffix; + xml_path = Glib::build_filename (xml_path, legalized_filename); string cmd ("ardour3 -P -U "); cmd += event->client_uuid; cmd += " \""; - cmd += xml_path.to_string(); + cmd += xml_path; cmd += '\"'; event->command_line = strdup (cmd.c_str()); @@ -771,7 +740,7 @@ int Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot) { XMLTree tree; - sys::path xml_path(_session_dir->root_path()); + std::string xml_path(_session_dir->root_path()); if (!_writable || (_state_of_the_state & CannotSave)) { return 1; @@ -787,8 +756,14 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot /* tell sources we're saving first, in case they write out to a new file * which should be saved with the state rather than the old one */ for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) { - i->second->session_saved(); - } + try { + i->second->session_saved(); + } catch (Evoral::SMF::FileError& e) { + error << string_compose ("Could not write to MIDI file %1; MIDI data not saved.", e.file_name ()) << endmsg; + } + } + + SaveSession (); /* EMIT SIGNAL */ tree.set_root (&get_state()); @@ -802,11 +777,11 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot /* proper save: use statefile_suffix (.ardour in English) */ - xml_path /= legalize_for_path (snapshot_name) + statefile_suffix; + xml_path = Glib::build_filename (xml_path, legalize_for_path (snapshot_name) + statefile_suffix); /* make a backup copy of the old file */ - if (sys::exists(xml_path) && !create_backup_file (xml_path)) { + if (Glib::file_test (xml_path, Glib::FILE_TEST_EXISTS) && !create_backup_file (xml_path)) { // create_backup_file will log the error return -1; } @@ -814,26 +789,31 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot } else { /* pending save: use pending_suffix (.pending in English) */ - xml_path /= legalize_for_path (snapshot_name) + pending_suffix; + xml_path = Glib::build_filename (xml_path, legalize_for_path (snapshot_name) + pending_suffix); } - sys::path tmp_path(_session_dir->root_path()); + std::string tmp_path(_session_dir->root_path()); + tmp_path = Glib::build_filename (tmp_path, legalize_for_path (snapshot_name) + temp_suffix); - tmp_path /= legalize_for_path (snapshot_name) + temp_suffix; + // cerr << "actually writing state to " << xml_path << endl; - // cerr << "actually writing state to " << xml_path.to_string() << endl; - - if (!tree.write (tmp_path.to_string())) { - error << string_compose (_("state could not be saved to %1"), tmp_path.to_string()) << endmsg; - sys::remove (tmp_path); + 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) { + error << string_compose(_("Could not remove temporary session file at path \"%1\" (%2)"), + tmp_path, g_strerror (errno)) << endmsg; + } return -1; } else { - if (::rename (tmp_path.to_string().c_str(), xml_path.to_string().c_str()) != 0) { - error << string_compose (_("could not rename temporary session file %1 to %2"), - tmp_path.to_string(), xml_path.to_string()) << endmsg; - sys::remove (tmp_path); + 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; + if (g_remove (tmp_path.c_str()) != 0) { + error << string_compose(_("Could not remove temporary session file at path \"%1\" (%2)"), + tmp_path, g_strerror (errno)) << endmsg; + } return -1; } } @@ -876,10 +856,10 @@ Session::load_state (string snapshot_name) /* check for leftover pending state from a crashed capture attempt */ - sys::path xmlpath(_session_dir->root_path()); - xmlpath /= legalize_for_path (snapshot_name) + pending_suffix; + std::string xmlpath(_session_dir->root_path()); + xmlpath = Glib::build_filename (xmlpath, legalize_for_path (snapshot_name) + pending_suffix); - if (sys::exists (xmlpath)) { + if (Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) { /* there is pending state from a crashed capture attempt */ @@ -890,15 +870,13 @@ Session::load_state (string snapshot_name) } if (!state_was_pending) { - xmlpath = _session_dir->root_path(); - xmlpath /= snapshot_name; + xmlpath = Glib::build_filename (_session_dir->root_path(), snapshot_name); } - if (!sys::exists (xmlpath)) { - xmlpath = _session_dir->root_path(); - xmlpath /= legalize_for_path (snapshot_name) + statefile_suffix; - if (!sys::exists (xmlpath)) { - error << string_compose(_("%1: session state information file \"%2\" doesn't exist!"), _name, xmlpath.to_string()) << endmsg; + if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) { + xmlpath = Glib::build_filename (_session_dir->root_path(), legalize_for_path (snapshot_name) + statefile_suffix); + if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) { + error << string_compose(_("%1: session file \"%2\" doesn't exist!"), _name, xmlpath) << endmsg; return 1; } } @@ -909,8 +887,8 @@ Session::load_state (string snapshot_name) _writable = exists_and_writable (xmlpath); - if (!state_tree->read (xmlpath.to_string())) { - error << string_compose(_("Could not understand ardour file %1"), xmlpath.to_string()) << endmsg; + if (!state_tree->read (xmlpath)) { + error << string_compose(_("Could not understand session file %1"), xmlpath) << endmsg; delete state_tree; state_tree = 0; return -1; @@ -919,7 +897,7 @@ Session::load_state (string snapshot_name) XMLNode& root (*state_tree->root()); if (root.name() != X_("Session")) { - error << string_compose (_("Session file %1 is not a session"), xmlpath.to_string()) << endmsg; + error << string_compose (_("Session file %1 is not a session"), xmlpath) << endmsg; delete state_tree; state_tree = 0; return -1; @@ -931,39 +909,33 @@ Session::load_state (string snapshot_name) /* no version implies very old version of Ardour */ Stateful::loading_state_version = 1000; } else { - int major; - int minor; - int micro; - - sscanf (prop->value().c_str(), "%d.%d.%d", &major, &minor, µ); - Stateful::loading_state_version = (major * 1000) + minor; + if (prop->value().find ('.') != string::npos) { + /* old school version format */ + if (prop->value()[0] == '2') { + Stateful::loading_state_version = 2000; + } else { + Stateful::loading_state_version = 3000; + } + } else { + Stateful::loading_state_version = atoi (prop->value()); + } } - if (Stateful::loading_state_version < CURRENT_SESSION_FILE_VERSION) { - - sys::path backup_path(_session_dir->root_path()); + if (Stateful::loading_state_version < CURRENT_SESSION_FILE_VERSION && _writable) { - backup_path /= legalize_for_path (snapshot_name) + "-1" + statefile_suffix; - - // only create a backup once - if (sys::exists (backup_path)) { - return 0; - } + std::string backup_path(_session_dir->root_path()); + std::string backup_filename = string_compose ("%1-%2%3", legalize_for_path (snapshot_name), Stateful::loading_state_version, statefile_suffix); + backup_path = Glib::build_filename (backup_path, backup_filename); - info << string_compose (_("Copying old session file %1 to %2\nUse %2 with %3 versions before 2.0 from now on"), - xmlpath.to_string(), backup_path.to_string(), PROGRAM_NAME) - << endmsg; + // only create a backup for a given statefile version once - try - { - sys::copy_file (xmlpath, backup_path); - } - catch(sys::filesystem_error& ex) - { - error << string_compose (_("Unable to make backup of state file %1 (%2)"), - xmlpath.to_string(), ex.what()) - << endmsg; - return -1; + if (!Glib::file_test (backup_path, Glib::FILE_TEST_EXISTS)) { + + VersionMismatch (xmlpath, backup_path); + + if (!copy_file (xmlpath, backup_path)) {; + return -1; + } } } @@ -998,15 +970,14 @@ Session::get_template() } XMLNode& -Session::state(bool full_state) +Session::state (bool full_state) { XMLNode* node = new XMLNode("Session"); XMLNode* child; - // store libardour version, just in case char buf[16]; - snprintf(buf, sizeof(buf), "%d.%d.%d", libardour3_major_version, libardour3_minor_version, libardour3_micro_version); - node->add_property("version", string(buf)); + snprintf(buf, sizeof(buf), "%d", CURRENT_SESSION_FILE_VERSION); + node->add_property("version", buf); /* store configuration settings */ @@ -1060,12 +1031,12 @@ Session::state(bool full_state) node->add_child_nocopy (config.get_variables ()); - node->add_child_nocopy (_metadata->get_state()); + node->add_child_nocopy (ARDOUR::SessionMetadata::Metadata()->get_state()); child = node->add_child ("Sources"); if (full_state) { - Glib::Mutex::Lock sl (source_lock); + Glib::Threads::Mutex::Lock sl (source_lock); for (SourceMap::iterator siter = sources.begin(); siter != sources.end(); ++siter) { @@ -1092,13 +1063,17 @@ Session::state(bool full_state) child = node->add_child ("Regions"); if (full_state) { - Glib::Mutex::Lock rl (region_lock); + Glib::Threads::Mutex::Lock rl (region_lock); const RegionFactory::RegionMap& region_map (RegionFactory::all_regions()); for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) { boost::shared_ptr r = i->second; /* only store regions not attached to playlists */ if (r->playlist() == 0) { - child->add_child_nocopy (r->state ()); + if (boost::dynamic_pointer_cast(r)) { + child->add_child_nocopy ((boost::dynamic_pointer_cast(r))->get_basic_state ()); + } else { + child->add_child_nocopy (r->get_state ()); + } } } @@ -1157,7 +1132,7 @@ Session::state(bool full_state) } for (RouteList::iterator i = public_order.begin(); i != public_order.end(); ++i) { - if (!(*i)->is_hidden()) { + if (!(*i)->is_auditioner()) { if (full_state) { child->add_child_nocopy ((*i)->get_state()); } else { @@ -1175,17 +1150,19 @@ Session::state(bool full_state) } if (_click_io) { - child = node->add_child ("Click"); - child->add_child_nocopy (_click_io->state (full_state)); + XMLNode* gain_child = node->add_child ("Click"); + gain_child->add_child_nocopy (_click_io->state (full_state)); + gain_child->add_child_nocopy (_click_gain->state (full_state)); } - if (full_state) { - child = node->add_child ("NamedSelections"); - for (NamedSelectionList::iterator i = named_selections.begin(); i != named_selections.end(); ++i) { - if (full_state) { - child->add_child_nocopy ((*i)->get_state()); - } - } + if (_ltc_input) { + XMLNode* ltc_input_child = node->add_child ("LTC-In"); + ltc_input_child->add_child_nocopy (_ltc_input->state (full_state)); + } + + if (_ltc_input) { + XMLNode* ltc_output_child = node->add_child ("LTC-Out"); + ltc_output_child->add_child_nocopy (_ltc_output->state (full_state)); } node->add_child_nocopy (_speakers->get_state()); @@ -1221,10 +1198,6 @@ Session::set_state (const XMLNode& node, int version) return -1; } - if ((prop = node.property ("version")) != 0) { - version = atoi (prop->value ()) * 1000; - } - if ((prop = node.property ("name")) != 0) { _name = prop->value (); } @@ -1241,7 +1214,7 @@ Session::set_state (const XMLNode& node, int version) } } - setup_raid_path(_session_dir->root_path().to_string()); + setup_raid_path(_session_dir->root_path()); if ((prop = node.property (X_("id-counter"))) != 0) { uint64_t x; @@ -1276,7 +1249,7 @@ Session::set_state (const XMLNode& node, int version) if (version >= 3000) { if ((child = find_named_node (node, "Metadata")) == 0) { warning << _("Session: XML state has no metadata section") << endmsg; - } else if (_metadata->set_state (*child, version)) { + } else if ( ARDOUR::SessionMetadata::Metadata()->set_state (*child, version) ) { goto out; } } @@ -1351,12 +1324,6 @@ Session::set_state (const XMLNode& node, int version) } } - if ((child = find_named_node (node, "NamedSelections")) != 0) { - if (load_named_selections (*child)) { - goto out; - } - } - if (version >= 3000) { if ((child = find_named_node (node, "Bundles")) == 0) { warning << _("Session: XML state has no bundles section") << endmsg; @@ -1417,13 +1384,21 @@ Session::set_state (const XMLNode& node, int version) if ((child = find_named_node (node, "Click")) == 0) { warning << _("Session: XML state has no click section") << endmsg; } else if (_click_io) { - _click_io->set_state (*child, version); + const XMLNodeList& children (child->children()); + XMLNodeList::const_iterator i = children.begin(); + _click_io->set_state (**i, version); + ++i; + if (i != children.end()) { + _click_gain->set_state (**i, version); + } } - if ((child = find_named_node (node, "ControlProtocols")) != 0) { - ControlProtocolManager::instance().set_protocol_states (*child); + if ((child = find_named_node (node, ControlProtocolManager::state_node_name)) != 0) { + ControlProtocolManager::instance().set_state (*child, version); } + update_have_rec_enabled_track (); + /* here beginneth the second phase ... */ StateReady (); /* EMIT SIGNAL */ @@ -1464,7 +1439,7 @@ Session::load_routes (const XMLNode& node, int version) new_routes.push_back (route); } - add_routes (new_routes, false, false); + add_routes (new_routes, false, false, false); return 0; } @@ -1679,7 +1654,7 @@ Session::load_nested_sources (const XMLNode& node) XMLProperty* prop = (*niter)->property (X_("id")); if (!prop) { - error << _("Nested source has no ID info in session state file! (ignored)") << endmsg; + error << _("Nested source has no ID info in session file! (ignored)") << endmsg; continue; } @@ -1911,7 +1886,7 @@ Session::get_sources_as_xml () { XMLNode* node = new XMLNode (X_("Sources")); - Glib::Mutex::Lock lm (source_lock); + Glib::Threads::Mutex::Lock lm (source_lock); for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) { node->add_child_nocopy (i->second->get_state()); @@ -1926,7 +1901,7 @@ Session::path_from_region_name (DataType type, string name, string identifier) char buf[PATH_MAX+1]; uint32_t n; SessionDirectory sdir(get_best_session_directory_for_new_source()); - sys::path source_dir = ((type == DataType::AUDIO) + 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); @@ -1940,10 +1915,10 @@ Session::path_from_region_name (DataType type, string name, string identifier) n, ext.c_str()); } - sys::path source_path = source_dir / buf; + std::string source_path = Glib::build_filename (source_dir, buf); - if (!sys::exists (source_path)) { - return source_path.to_string(); + if (!Glib::file_test (source_path, Glib::FILE_TEST_EXISTS)) { + return source_path; } } @@ -2041,49 +2016,54 @@ Session::save_template (string template_name) return -1; } - sys::path user_template_dir(user_template_directory()); + std::string user_template_dir(user_template_directory()); - try - { - sys::create_directories (user_template_dir); - } - catch(sys::filesystem_error& ex) - { + if (g_mkdir_with_parents (user_template_dir.c_str(), 0755) != 0) { error << string_compose(_("Could not create templates directory \"%1\" (%2)"), - user_template_dir.to_string(), ex.what()) << endmsg; + user_template_dir, g_strerror (errno)) << endmsg; return -1; } tree.set_root (&get_template()); - sys::path template_dir_path(user_template_dir); + std::string template_dir_path(user_template_dir); /* directory to put the template in */ - template_dir_path /= template_name; - if (sys::exists (template_dir_path)) - { + template_dir_path = Glib::build_filename (template_dir_path, template_name); + + if (Glib::file_test (template_dir_path, Glib::FILE_TEST_EXISTS)) { warning << string_compose(_("Template \"%1\" already exists - new version not created"), - template_dir_path.to_string()) << endmsg; + template_dir_path) << endmsg; return -1; } - sys::create_directories (template_dir_path); + if (g_mkdir_with_parents (template_dir_path.c_str(), 0755) != 0) { + error << string_compose(_("Could not create directory for Session template\"%1\" (%2)"), + template_dir_path, g_strerror (errno)) << endmsg; + return -1; + } /* file to write */ - sys::path template_file_path = template_dir_path; - template_file_path /= template_name + template_suffix; + std::string template_file_path(template_dir_path); + template_file_path = Glib::build_filename (template_file_path, template_name + template_suffix); - if (!tree.write (template_file_path.to_string())) { + if (!tree.write (template_file_path)) { error << _("template not saved") << endmsg; return -1; } /* copy plugin state directory */ - sys::path template_plugin_state_path = template_dir_path; - template_plugin_state_path /= X_("plugins"); - sys::create_directories (template_plugin_state_path); - sys::copy_files (plugins_dir(), template_plugin_state_path); + std::string template_plugin_state_path(template_dir_path); + template_plugin_state_path = Glib::build_filename (template_plugin_state_path, X_("plugins")); + + if (g_mkdir_with_parents (template_plugin_state_path.c_str(), 0755) != 0) { + error << string_compose(_("Could not create directory for Session template plugin state\"%1\" (%2)"), + template_plugin_state_path, g_strerror (errno)) << endmsg; + return -1; + } + + copy_files (plugins_dir(), template_plugin_state_path); return 0; } @@ -2091,23 +2071,48 @@ Session::save_template (string template_name) void Session::refresh_disk_space () { -#if HAVE_SYS_VFS_H - struct statfs statfsbuf; - vector::iterator i; - Glib::Mutex::Lock lm (space_lock); - double scale; +#if __APPLE__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H) + + Glib::Threads::Mutex::Lock lm (space_lock); /* get freespace on every FS that is part of the session path */ _total_free_4k_blocks = 0; + _total_free_4k_blocks_uncertain = false; - for (i = session_dirs.begin(); i != session_dirs.end(); ++i) { - statfs ((*i).path.c_str(), &statfsbuf); + for (vector::iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) { + + struct statfs statfsbuf; + statfs (i->path.c_str(), &statfsbuf); - scale = statfsbuf.f_bsize/4096.0; + double const scale = statfsbuf.f_bsize / 4096.0; - (*i).blocks = (uint32_t) floor (statfsbuf.f_bavail * scale); - _total_free_4k_blocks += (*i).blocks; + /* See if this filesystem is read-only */ + struct statvfs statvfsbuf; + statvfs (i->path.c_str(), &statvfsbuf); + + /* f_bavail can be 0 if it is undefined for whatever + filesystem we are looking at; Samba shares mounted + via GVFS are an example of this. + */ + if (statfsbuf.f_bavail == 0) { + /* block count unknown */ + i->blocks = 0; + i->blocks_unknown = true; + } else if (statvfsbuf.f_flag & ST_RDONLY) { + /* read-only filesystem */ + i->blocks = 0; + i->blocks_unknown = false; + } else { + /* read/write filesystem with known space */ + i->blocks = (uint32_t) floor (statfsbuf.f_bavail * scale); + i->blocks_unknown = false; + } + + _total_free_4k_blocks += i->blocks; + if (i->blocks_unknown) { + _total_free_4k_blocks_uncertain = true; + } } #endif } @@ -2116,7 +2121,7 @@ string Session::get_best_session_directory_for_new_source () { vector::iterator i; - string result = _session_dir->root_path().to_string(); + string result = _session_dir->root_path(); /* handle common case without system calls */ @@ -2175,7 +2180,8 @@ Session::get_best_session_directory_for_new_source () } if ((*i).blocks * 4096 >= Config->get_disk_choice_space_threshold()) { - if (create_session_directory ((*i).path)) { + SessionDirectory sdir(i->path); + if (sdir.create ()) { result = (*i).path; last_rr_session_dir = i; return result; @@ -2197,7 +2203,8 @@ Session::get_best_session_directory_for_new_source () sort (sorted.begin(), sorted.end(), cmp); for (i = sorted.begin(); i != sorted.end(); ++i) { - if (create_session_directory ((*i).path)) { + SessionDirectory sdir(i->path); + if (sdir.create ()) { result = (*i).path; last_rr_session_dir = i; return result; @@ -2208,39 +2215,6 @@ Session::get_best_session_directory_for_new_source () return result; } -int -Session::load_named_selections (const XMLNode& node) -{ - XMLNodeList nlist; - XMLNodeConstIterator niter; - NamedSelection *ns; - - nlist = node.children(); - - set_dirty(); - - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - - if ((ns = XMLNamedSelectionFactory (**niter)) == 0) { - error << _("Session: cannot create Named Selection from XML description.") << endmsg; - } - } - - return 0; -} - -NamedSelection * -Session::XMLNamedSelectionFactory (const XMLNode& node) -{ - try { - return new NamedSelection (*this, node); - } - - catch (failed_constructor& err) { - return 0; - } -} - string Session::automation_dir () const { @@ -2259,6 +2233,12 @@ Session::plugins_dir () const return Glib::build_filename (_path, "plugins"); } +string +Session::externals_dir () const +{ + return Glib::build_filename (_path, "externals"); +} + int Session::load_bundles (XMLNode const & node) { @@ -2273,7 +2253,7 @@ Session::load_bundles (XMLNode const & node) } else if ((*niter)->name() == "OutputBundle") { add_bundle (boost::shared_ptr (new UserBundle (**niter, false))); } else { - error << string_compose(_("Unknown node \"%1\" found in Bundles list from state file"), (*niter)->name()) << endmsg; + error << string_compose(_("Unknown node \"%1\" found in Bundles list from session file"), (*niter)->name()) << endmsg; return -1; } } @@ -2600,7 +2580,7 @@ Session::find_all_sources_across_snapshots (set& result, bool exclude_th ripped = ripped.substr (0, ripped.length() - 1); } - state_files = scanner (ripped, accept_all_state_files, (void *) 0, false, true); + state_files = scanner (ripped, accept_all_state_files, (void *) 0, true, true); if (state_files == 0) { /* impossible! */ @@ -2679,6 +2659,8 @@ Session::cleanup_sources (CleanupReport& rep) bool used; string spath; int ret = -1; + string tmppath1; + string tmppath2; _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup); @@ -2726,7 +2708,7 @@ Session::cleanup_sources (CleanupReport& rep) ++nexti; SessionDirectory sdir ((*i).path); - audio_path += sdir.sound_path().to_string(); + audio_path += sdir.sound_path(); if (nexti != session_dirs.end()) { audio_path += ':'; @@ -2744,7 +2726,7 @@ Session::cleanup_sources (CleanupReport& rep) ++nexti; SessionDirectory sdir ((*i).path); - midi_path += sdir.midi_path().to_string(); + midi_path += sdir.midi_path(); if (nexti != session_dirs.end()) { midi_path += ':'; @@ -2803,9 +2785,6 @@ Session::cleanup_sources (CleanupReport& rep) i = tmp; } - char tmppath1[PATH_MAX+1]; - char tmppath2[PATH_MAX+1]; - if (candidates) { for (vector::iterator x = candidates->begin(); x != candidates->end(); ++x) { @@ -2814,19 +2793,10 @@ Session::cleanup_sources (CleanupReport& rep) for (set::iterator i = all_sources.begin(); i != all_sources.end(); ++i) { - if (realpath(spath.c_str(), tmppath1) == 0) { - error << string_compose (_("Cannot expand path %1 (%2)"), - spath, strerror (errno)) << endmsg; - continue; - } - - if (realpath((*i).c_str(), tmppath2) == 0) { - error << string_compose (_("Cannot expand path %1 (%2)"), - (*i), strerror (errno)) << endmsg; - continue; - } + tmppath1 = canonical_path (spath); + tmppath2 = canonical_path ((*i)); - if (strcmp(tmppath1, tmppath2) == 0) { + if (tmppath1 == tmppath2) { used = true; break; } @@ -2930,7 +2900,7 @@ Session::cleanup_sources (CleanupReport& rep) string peakpath = peak_path (base); if (Glib::file_test (peakpath.c_str(), Glib::FILE_TEST_EXISTS)) { - if (::unlink (peakpath.c_str()) != 0) { + if (::g_unlink (peakpath.c_str()) != 0) { error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"), peakpath, _path, strerror (errno)) << endmsg; @@ -3030,7 +3000,7 @@ Session::add_controllable (boost::shared_ptr c) as part of the session. */ - Glib::Mutex::Lock lm (controllables_lock); + Glib::Threads::Mutex::Lock lm (controllables_lock); controllables.insert (c); } @@ -3039,11 +3009,11 @@ struct null_deleter { void operator()(void const *) const {} }; void Session::remove_controllable (Controllable* c) { - if (_state_of_the_state | Deletion) { + if (_state_of_the_state & Deletion) { return; } - Glib::Mutex::Lock lm (controllables_lock); + Glib::Threads::Mutex::Lock lm (controllables_lock); Controllables::iterator x = controllables.find (boost::shared_ptr(c, null_deleter())); @@ -3055,7 +3025,7 @@ Session::remove_controllable (Controllable* c) boost::shared_ptr Session::controllable_by_id (const PBD::ID& id) { - Glib::Mutex::Lock lm (controllables_lock); + Glib::Threads::Mutex::Lock lm (controllables_lock); for (Controllables::iterator i = controllables.begin(); i != controllables.end(); ++i) { if ((*i)->id() == id) { @@ -3179,7 +3149,7 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc) if (p) { boost::shared_ptr s = boost::dynamic_pointer_cast(p); boost::shared_ptr a = s->amp(); - + if (a) { c = s->amp()->gain_control(); } @@ -3228,16 +3198,11 @@ Session::save_history (string snapshot_name) const string history_filename = legalize_for_path (snapshot_name) + history_suffix; const string backup_filename = history_filename + backup_suffix; - const sys::path xml_path = _session_dir->root_path() / history_filename; - const sys::path backup_path = _session_dir->root_path() / backup_filename; + const std::string xml_path(Glib::build_filename (_session_dir->root_path(), history_filename)); + const std::string backup_path(Glib::build_filename (_session_dir->root_path(), backup_filename)); - if (sys::exists (xml_path)) { - try - { - sys::rename (xml_path, backup_path); - } - catch (const sys::filesystem_error& err) - { + if (Glib::file_test (xml_path, Glib::FILE_TEST_EXISTS)) { + if (::g_rename (xml_path.c_str(), backup_path.c_str()) != 0) { error << _("could not backup old history file, current history not saved") << endmsg; return -1; } @@ -3249,19 +3214,17 @@ Session::save_history (string snapshot_name) tree.set_root (&_history.get_state (Config->get_saved_history_depth())); - if (!tree.write (xml_path.to_string())) + if (!tree.write (xml_path)) { - error << string_compose (_("history could not be saved to %1"), xml_path.to_string()) << endmsg; + error << string_compose (_("history could not be saved to %1"), xml_path) << endmsg; - try - { - sys::remove (xml_path); - sys::rename (backup_path, xml_path); + if (g_remove (xml_path.c_str()) != 0) { + error << string_compose(_("Could not remove history file at path \"%1\" (%2)"), + xml_path, g_strerror (errno)) << endmsg; } - catch (const sys::filesystem_error& err) - { + if (::g_rename (backup_path.c_str(), xml_path.c_str()) != 0) { error << string_compose (_("could not restore history file from backup %1 (%2)"), - backup_path.to_string(), err.what()) << endmsg; + backup_path, g_strerror (errno)) << endmsg; } return -1; @@ -3279,20 +3242,20 @@ Session::restore_history (string snapshot_name) snapshot_name = _current_snapshot_name; } - const string xml_filename = legalize_for_path (snapshot_name) + history_suffix; - const sys::path xml_path = _session_dir->root_path() / xml_filename; + const std::string xml_filename = legalize_for_path (snapshot_name) + history_suffix; + const std::string xml_path(Glib::build_filename (_session_dir->root_path(), xml_filename)); - info << "Loading history from " << xml_path.to_string() << endmsg; + info << "Loading history from " << xml_path << endmsg; - if (!sys::exists (xml_path)) { + if (!Glib::file_test (xml_path, Glib::FILE_TEST_EXISTS)) { info << string_compose (_("%1: no history file \"%2\" for this session."), - _name, xml_path.to_string()) << endmsg; + _name, xml_path) << endmsg; return 1; } - if (!tree.read (xml_path.to_string())) { + if (!tree.read (xml_path)) { error << string_compose (_("Could not understand session history file \"%1\""), - xml_path.to_string()) << endmsg; + xml_path) << endmsg; return -1; } @@ -3421,7 +3384,7 @@ Session::config_changed (std::string p, bool ours) } else if (p == "edit-mode") { - Glib::Mutex::Lock lm (playlists->lock); + Glib::Threads::Mutex::Lock lm (playlists->lock); for (SessionPlaylists::List::iterator i = playlists->playlists.begin(); i != playlists->playlists.end(); ++i) { (*i)->set_edit_mode (Config->get_edit_mode ()); @@ -3489,6 +3452,12 @@ Session::config_changed (std::string p, bool ours) _clicking = false; } + } else if (p == "click-gain") { + + if (_click_gain) { + _click_gain->set_gain (Config->get_click_gain(), this); + } + } else if (p == "send-mtc") { if (Config->get_send_mtc ()) { @@ -3528,16 +3497,32 @@ Session::config_changed (std::string p, bool ours) if (!config.get_external_sync()) { drop_sync_source (); } else { - switch_to_sync_source (config.get_sync_source()); + switch_to_sync_source (Config->get_sync_source()); } - } else if (p == "remote-model") { - set_remote_control_ids (); } else if (p == "denormal-model") { setup_fpu (); } else if (p == "history-depth") { set_history_depth (Config->get_history_depth()); + } else if (p == "remote-model") { + /* XXX DO SOMETHING HERE TO TELL THE GUI THAT WE NEED + TO SET REMOTE ID'S + */ } else if (p == "sync-all-route-ordering") { - sync_order_keys ("session"); + + /* sync to editor order unless mixer is used for remote IDs + */ + + switch (Config->get_remote_model()) { + case UserOrdered: + sync_order_keys (EditorSort); + break; + case EditorOrdered: + sync_order_keys (EditorSort); + break; + case MixerOrdered: + sync_order_keys (MixerSort); + } + } else if (p == "initial-program-change") { if (MIDI::Manager::instance()->mmc()->output_port() && Config->get_initial_program_change() >= 0) { @@ -3558,6 +3543,14 @@ Session::config_changed (std::string p, bool ours) last_timecode_valid = false; } else if (p == "playback-buffer-seconds") { AudioSource::allocate_working_buffers (frame_rate()); + } else if (p == "automation-thinning-factor") { + Evoral::ControlList::set_thinning_factor (Config->get_automation_thinning_factor()); + } else if (p == "ltc-source-port") { + reconnect_ltc_input (); + } else if (p == "ltc-sink-port") { + reconnect_ltc_output (); + } else if (p == "timecode-generator-offset") { + ltc_tx_parse_offset(); } set_dirty (); @@ -3620,9 +3613,9 @@ Session::setup_midi_machine_control () /* also handle MIDI SPP because its so common */ - mmc->SPPStart.connect_same_thread (*this, boost::bind (&Session::spp_start, this, _1, _2)); - mmc->SPPContinue.connect_same_thread (*this, boost::bind (&Session::spp_continue, this, _1, _2)); - mmc->SPPStop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this, _1, _2)); + mmc->SPPStart.connect_same_thread (*this, boost::bind (&Session::spp_start, this)); + mmc->SPPContinue.connect_same_thread (*this, boost::bind (&Session::spp_continue, this)); + mmc->SPPStop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this)); } boost::shared_ptr @@ -3649,7 +3642,7 @@ Session::rename (const std::string& new_name) string newstr; bool first = true; - string const old_sources_root = _session_dir->sources_root().to_string (); + string const old_sources_root = _session_dir->sources_root(); #define RENAME ::rename @@ -3778,7 +3771,7 @@ Session::rename (const std::string& new_name) boost::shared_ptr fs = boost::dynamic_pointer_cast (i->second); if (fs) { string p = fs->path (); - boost::replace_all (p, old_sources_root, _session_dir->sources_root().to_string ()); + boost::replace_all (p, old_sources_root, _session_dir->sources_root()); fs->set_path (p); } }