#include <stdint.h>
#include <algorithm>
-#include <fstream>
#include <string>
#include <cerrno>
#include <cstdio> /* snprintf(3) ... grrr */
#include <cmath>
#include <unistd.h>
-#include <sys/stat.h>
#include <climits>
#include <signal.h>
#include <sys/time.h>
#endif
#include <glib.h>
-#include <glib/gstdio.h>
+#include "pbd/gstdio_compat.h"
#include <glibmm.h>
#include <glibmm/threads.h>
#include "pbd/boost_debug.h"
#include "pbd/basename.h"
#include "pbd/controllable_descriptor.h"
+#include "pbd/debug.h"
#include "pbd/enumwriter.h"
#include "pbd/error.h"
#include "pbd/file_utils.h"
using namespace ARDOUR;
using namespace PBD;
+#define DEBUG_UNDO_HISTORY(msg) DEBUG_TRACE (PBD::DEBUG::UndoHistory, string_compose ("%1: %2\n", __LINE__, msg));
+
void
Session::pre_engine_init (string fullpath)
{
_path = canonical_path(fullpath);
/* is it new ? */
+ if (Profile->get_trx() ) {
+ // Waves TracksLive has a usecase of session replacement with a new one.
+ // We should check session state file (<session_name>.ardour) existance
+ // to determine if the session is new or not
- _is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
+ string full_session_name = Glib::build_filename( fullpath, _name );
+ full_session_name += statefile_suffix;
+
+ _is_new = !Glib::file_test (full_session_name, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
+ } else {
+ _is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
+ }
/* finish initialization that can't be done in a normal C++ constructor
definition.
last_rr_session_dir = session_dirs.begin();
set_history_depth (Config->get_history_depth());
-
+
/* default: assume simple stereo speaker configuration */
_speakers->setup_default_speakers (2);
Delivery::disable_panners ();
IO::disable_connecting ();
-
- AudioFileSource::set_peak_dir (_session_dir->peak_path());
}
int
BootMessage (_("Using configuration"));
_midi_ports = new MidiPortManager;
-
+
MIDISceneChanger* msc;
_scene_changer = msc = new MIDISceneChanger (*this);
boost::dynamic_pointer_cast<AsyncMIDIPort>(scene_in())->set_timer (timer_func);
setup_midi_machine_control ();
-
+
if (_butler->start_thread()) {
return -1;
}
-
+
if (start_midi_thread ()) {
return -1;
}
-
+
setup_click_sounds (0);
setup_midi_control ();
delete _tempo_map;
_tempo_map = new TempoMap (_current_frame_rate);
_tempo_map->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1));
-
+
/* MidiClock requires a tempo map */
midi_clock = new MidiClockTicker ();
SndFileSource::setup_standard_crossfades (*this, frame_rate());
_engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this));
-
+
AudioDiskstream::allocate_working_buffers();
refresh_disk_space ();
-
+
/* we're finally ready to call set_state() ... all objects have
* been created, the engine is running.
*/
-
+
if (state_tree) {
if (set_state (*state_tree->root(), Stateful::loading_state_version)) {
return -1;
boost::function<void (std::string)> ff (boost::bind (&Session::config_changed, this, _1, false));
boost::function<void (std::string)> ft (boost::bind (&Session::config_changed, this, _1, true));
-
+
Config->map_parameters (ff);
config.map_parameters (ft);
_butler->map_parameters ();
/* Reset all panners */
-
+
Delivery::reset_panners ();
-
+
/* this will cause the CPM to instantiate any protocols that are in use
* (or mandatory), which will pass it this Session, and then call
* set_state() on each instantiated protocol to match stored state.
*/
-
+
ControlProtocolManager::instance().set_session (this);
-
+
/* This must be done after the ControlProtocolManager set_session above,
as it will set states for ports which the ControlProtocolManager creates.
*/
-
+
// XXX set state of MIDI::Port's
// MidiPortManager::instance()->set_port_states (Config->midi_port_states ());
-
+
/* And this must be done after the MIDI::Manager::set_port_states as
* it will try to make connections whose details are loaded by set_port_states.
*/
-
+
hookup_io ();
-
+
/* Let control protocols know that we are now all connected, so they
* could start talking to surfaces if they want to.
*/
-
+
ControlProtocolManager::instance().midi_connectivity_established ();
-
+
if (_is_new && !no_auto_connect()) {
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock());
auto_connect_master_bus ();
}
-
+
_state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty));
-
+
/* update latencies */
-
+
initialize_latencies ();
-
+
_locations->added.connect_same_thread (*this, boost::bind (&Session::location_added, this, _1));
_locations->removed.connect_same_thread (*this, boost::bind (&Session::location_removed, this, _1));
_locations->changed.connect_same_thread (*this, boost::bind (&Session::locations_changed, this));
-
+
} catch (AudioEngine::PortRegistrationFailure& err) {
/* handle this one in a different way than all others, so that its clear what happened */
error << err.what() << endmsg;
send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset));
send_immediate_mmc (MIDI::MachineControlCommand (Timecode::Time ()));
- MIDI::Name::MidiPatchManager::instance().set_session (this);
+ MIDI::Name::MidiPatchManager::instance().add_search_path (session_directory().midi_patch_path() );
ltc_tx_initialize();
/* initial program change will be delivered later; see ::config_changed() */
}
/* Now, finally, we can fill the playback buffers */
-
+
BootMessage (_("Filling playback buffers"));
-
+
boost::shared_ptr<RouteList> rl = routes.reader();
for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<Track> (*r);
Session::session_loaded ()
{
SessionLoaded();
-
+
_state_of_the_state = Clean;
-
+
DirtyChanged (); /* EMIT SIGNAL */
-
+
if (_is_new) {
save_state ("");
} else if (state_was_pending) {
remove_pending_capture_state ();
state_was_pending = false;
}
-
+
/* Now, finally, we can fill the playback buffers */
-
+
BootMessage (_("Filling playback buffers"));
-
- boost::shared_ptr<RouteList> rl = routes.reader();
- for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
- boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<Track> (*r);
- if (trk && !trk->hidden()) {
- trk->seek (_transport_frame, true);
- }
- }
+ force_locate (_transport_frame, false);
}
string
_writable = exists_and_writable (_path);
if (!session_template.empty()) {
- std::string in_path = (ARDOUR::Profile->get_trx () ? session_template : session_template_dir_to_file (session_template));
+ string in_path = (ARDOUR::Profile->get_trx () ? session_template : session_template_dir_to_file (session_template));
- ifstream in(in_path.c_str());
+ FILE* in = g_fopen (in_path.c_str(), "rb");
if (in) {
/* no need to call legalize_for_path() since the string
*/
string out_path = Glib::build_filename (_session_dir->root_path(), _name + statefile_suffix);
- ofstream out(out_path.c_str());
+ FILE* out = g_fopen (out_path.c_str(), "wb");
if (out) {
- out << in.rdbuf();
- _is_new = false;
-
- if (!ARDOUR::Profile->get_trx()) {
- /* Copy plugin state files from template to new session */
- std::string template_plugins = Glib::build_filename (session_template, X_("plugins"));
- copy_recurse (template_plugins, plugins_dir ());
- }
-
+ char buf[1024];
+ stringstream new_session;
+
+ while (!feof (in)) {
+ size_t charsRead = fread (buf, sizeof(char), 1024, in);
+
+ if (ferror (in)) {
+ error << string_compose (_("Error reading session template file %1 (%2)"), in_path, strerror (errno)) << endmsg;
+ fclose (in);
+ fclose (out);
+ return -1;
+ }
+ if (charsRead == 0) {
+ break;
+ }
+ new_session.write (buf, charsRead);
+ }
+ fclose (in);
+
+ string file_contents = new_session.str();
+ size_t writeSize = file_contents.length();
+ if (fwrite (file_contents.c_str(), sizeof(char), writeSize, out) != writeSize) {
+ error << string_compose (_("Error writing session template file %1 (%2)"), out_path, strerror (errno)) << endmsg;
+ fclose (out);
+ return -1;
+ }
+ fclose (out);
+
+ _is_new = false;
+
+ if (!ARDOUR::Profile->get_trx()) {
+ /* Copy plugin state files from template to new session */
+ std::string template_plugins = Glib::build_filename (session_template, X_("plugins"));
+ copy_recurse (template_plugins, plugins_dir ());
+ }
+
return 0;
} else {
error << string_compose (_("Could not open %1 for writing session template"), out_path)
<< endmsg;
+ fclose(in);
return -1;
}
}
if (Profile->get_trx()) {
-
+
/* set initial start + end point : ARDOUR::Session::session_end_shift long.
Remember that this is a brand new session. Sessions
loaded from saved state will get this range from the saved state.
*/
-
+
set_session_range_location (0, 0);
-
+
/* Initial loop location, from absolute zero, length 10 seconds */
-
+
Location* loc = new Location (*this, 0, 10.0 * _engine.sample_rate(), _("Loop"), Location::IsAutoLoop);
_locations->add (loc, true);
set_auto_loop_location (loc);
// Waves Tracks: Skip this. Always use autoconnection for Tracks
if (!ARDOUR::Profile->get_trx()) {
-
+
/* this allows the user to override settings with an environment variable.
*/
-
+
if (no_auto_connect()) {
bus_profile->input_ac = AutoConnectOption (0);
bus_profile->output_ac = AutoConnectOption (0);
}
-
+
Config->set_input_auto_connect (bus_profile->input_ac);
Config->set_output_auto_connect (bus_profile->output_ac);
}
SessionSaveUnderway (); /* EMIT SIGNAL */
+ bool mark_as_clean = true;
+
+ if (!snapshot_name.empty() && !switch_to_snapshot) {
+ mark_as_clean = false;
+ }
+
if (template_only) {
+ mark_as_clean = false;
tree.set_root (&get_template());
} else {
tree.set_root (&get_state());
_current_snapshot_name = snapshot_name;
}
+ assert (!snapshot_name.empty());
+
if (!pending) {
/* proper save: use statefile_suffix (.ardour in English) */
tmp_path = Glib::build_filename (tmp_path, legalize_for_path (snapshot_name) + temp_suffix);
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) {
} 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;
save_history (snapshot_name);
- bool was_dirty = dirty();
+ if (mark_as_clean) {
+ bool was_dirty = dirty();
- _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
+ _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
- if (was_dirty) {
- DirtyChanged (); /* EMIT SIGNAL */
+ if (was_dirty) {
+ DirtyChanged (); /* EMIT SIGNAL */
+ }
}
StateSaved (snapshot_name); /* EMIT SIGNAL */
// only create a backup for a given statefile version once
if (!Glib::file_test (backup_path, Glib::FILE_TEST_EXISTS)) {
-
+
VersionMismatch (xmlpath, backup_path);
-
+
if (!copy_file (xmlpath, backup_path)) {;
return -1;
}
}
}
}
-
-
+
+
if (full_state) {
-
+
if (_locations) {
- node->add_child_nocopy (_locations->get_state());
+ node->add_child_nocopy (_locations->get_state());
}
} else {
Locations loc (*this);
range->set (max_framepos, 0);
loc.add (range);
XMLNode& locations_state = loc.get_state();
-
+
if (ARDOUR::Profile->get_trx() && _locations) {
// For tracks we need stored the Auto Loop Range and all MIDI markers.
for (Locations::LocationList::const_iterator i = _locations->list ().begin (); i != _locations->list ().end (); ++i) {
RoutePublicOrderSorter cmp;
RouteList public_order (*r);
public_order.sort (cmp);
-
+
/* the sort should have put control outs first */
-
+
if (_monitor_out) {
assert (_monitor_out == public_order.front());
}
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
if ((*niter)->name() == "Source") {
- /* it may already exist, so don't recreate it unnecessarily
+ /* it may already exist, so don't recreate it unnecessarily
*/
XMLProperty* prop = (*niter)->property (X_("id"));
}
int
-Session::save_template (string template_name)
+Session::save_template (string template_name, bool replace_existing)
{
if ((_state_of_the_state & CannotSave) || template_name.empty ()) {
return -1;
}
if (!ARDOUR::Profile->get_trx()) {
- if (Glib::file_test (template_dir_path, Glib::FILE_TEST_EXISTS)) {
+ if (!replace_existing && Glib::file_test (template_dir_path, Glib::FILE_TEST_EXISTS)) {
warning << string_compose(_("Template \"%1\" already exists - new version not created"),
template_dir_path) << endmsg;
- return -1;
+ return -2;
}
-
+
if (g_mkdir_with_parents (template_dir_path.c_str(), 0755) != 0) {
error << string_compose(_("Could not create directory for Session template\"%1\" (%2)"),
template_dir_path, g_strerror (errno)) << endmsg;
/* file to write */
std::string template_file_path;
-
+
if (ARDOUR::Profile->get_trx()) {
template_file_path = template_name;
} else {
}
SessionSaveUnderway (); /* EMIT SIGNAL */
-
+
XMLTree tree;
tree.set_root (&get_template());
Session::refresh_disk_space ()
{
#if __APPLE__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
-
+
Glib::Threads::Mutex::Lock lm (space_lock);
/* get freespace on every FS that is part of the session path */
}
}
+void
+Session::add_command (Command* const cmd)
+{
+ assert (_current_trans);
+ DEBUG_UNDO_HISTORY (
+ string_compose ("Current Undo Transaction %1, adding command: %2",
+ _current_trans->name (),
+ cmd->name ()));
+ _current_trans->add_command (cmd);
+}
void
Session::begin_reversible_command (const string& name)
{
*/
if (_current_trans == 0) {
+ DEBUG_UNDO_HISTORY (string_compose (
+ "Begin Reversible Command, new transaction: %1", g_quark_to_string (q)));
+
/* start a new transaction */
assert (_current_trans_quarks.empty ());
_current_trans = new UndoTransaction();
_current_trans->set_name (g_quark_to_string (q));
+ } else {
+ DEBUG_UNDO_HISTORY (
+ string_compose ("Begin Reversible Command, current transaction: %1",
+ _current_trans->name ()));
}
_current_trans_quarks.push_front (q);
Session::abort_reversible_command ()
{
if (_current_trans != 0) {
+ DEBUG_UNDO_HISTORY (
+ string_compose ("Abort Reversible Command: %1", _current_trans->name ()));
_current_trans->clear();
delete _current_trans;
_current_trans = 0;
struct timeval now;
if (cmd) {
+ DEBUG_UNDO_HISTORY (
+ string_compose ("Current Undo Transaction %1, adding command: %2",
+ _current_trans->name (),
+ cmd->name ()));
_current_trans->add_command (cmd);
}
+ DEBUG_UNDO_HISTORY (
+ string_compose ("Commit Reversible Command, current transaction: %1",
+ _current_trans->name ()));
+
_current_trans_quarks.pop_front ();
if (!_current_trans_quarks.empty ()) {
+ DEBUG_UNDO_HISTORY (
+ string_compose ("Commit Reversible Command, transaction is not "
+ "top-level, current transaction: %1",
+ _current_trans->name ()));
/* the transaction we're committing is not the top-level one */
return;
}
if (_current_trans->empty()) {
/* no commands were added to the transaction, so just get rid of it */
+ DEBUG_UNDO_HISTORY (
+ string_compose ("Commit Reversible Command, No commands were "
+ "added to current transaction: %1",
+ _current_trans->name ()));
delete _current_trans;
_current_trans = 0;
return;
bool removed = false;
const RegionFactory::RegionMap& regions (RegionFactory::regions());
- for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end();) {
uint32_t used = playlists->region_use_count (i->second);
if (used == 0 && !i->second->automatic ()) {
+ boost::weak_ptr<Region> w = i->second;
+ ++i;
removed = true;
- RegionFactory::map_remove (i->second);
+ RegionFactory::map_remove (w);
+ } else {
+ ++i;
}
}
if (removed) {
// re-check to remove parent references of compound regions
- for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end();) {
if (!(i->second->whole_file() && i->second->max_source_level() > 0)) {
+ ++i;
continue;
}
assert(boost::dynamic_pointer_cast<PlaylistSource>(i->second->source (0)) != 0);
if (0 == playlists->region_use_count (i->second)) {
- RegionFactory::map_remove (i->second);
+ boost::weak_ptr<Region> w = i->second;
+ ++i;
+ RegionFactory::map_remove (w);
+ } else {
+ ++i;
}
}
}
save_state ("");
}
+bool
+Session::can_cleanup_peakfiles () const
+{
+ if (deletion_in_progress()) {
+ return false;
+ }
+ if (!_writable || (_state_of_the_state & CannotSave)) {
+ warning << _("Cannot cleanup peak-files for read-only session.") << endmsg;
+ return false;
+ }
+ if (record_status() == Recording) {
+ error << _("Cannot cleanup peak-files while recording") << endmsg;
+ return false;
+ }
+ return true;
+}
+
+int
+Session::cleanup_peakfiles ()
+{
+ Glib::Threads::Mutex::Lock lm (peak_cleanup_lock, Glib::Threads::TRY_LOCK);
+ if (!lm.locked()) {
+ return -1;
+ }
+
+ assert (can_cleanup_peakfiles ());
+ assert (!peaks_cleanup_in_progres());
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state | PeakCleanup);
+
+ int timeout = 5000; // 5 seconds
+ while (!SourceFactory::files_with_peaks.empty()) {
+ Glib::usleep (1000);
+ if (--timeout < 0) {
+ warning << _("Timeout waiting for peak-file creation to terminate before cleanup, please try again later.") << endmsg;
+ _state_of_the_state = StateOfTheState (_state_of_the_state & (~PeakCleanup));
+ return -1;
+ }
+ }
+
+ for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+ boost::shared_ptr<AudioSource> as;
+ if ((as = boost::dynamic_pointer_cast<AudioSource> (i->second)) != 0) {
+ as->close_peakfile();
+ }
+ }
+
+ PBD::clear_directory (session_directory().peak_path());
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state & (~PeakCleanup));
+
+ for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+ boost::shared_ptr<AudioSource> as;
+ if ((as = boost::dynamic_pointer_cast<AudioSource> (i->second)) != 0) {
+ SourceFactory::setup_peakfile(as, true);
+ }
+ }
+ return 0;
+}
+
int
Session::cleanup_sources (CleanupReport& rep)
{
_state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
+ /* this is mostly for windows which doesn't allow file
+ * renaming if the file is in use. But we don't special
+ * case it because we need to know if this causes
+ * problems, and the easiest way to notice that is to
+ * keep it in place for all platforms.
+ */
+
+ request_stop (false);
+ _butler->summon ();
+ _butler->wait_until_finished ();
+
/* consider deleting all unused playlists */
if (playlists->maybe_delete_unused (boost::bind (Session::ask_about_playlist_deletion, _1))) {
if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) != 0) {
+ /* this is mostly for windows which doesn't allow file
+ * renaming if the file is in use. But we don't special
+ * case it because we need to know if this causes
+ * problems, and the easiest way to notice that is to
+ * keep it in place for all platforms.
+ */
+
+ fs->close ();
+
if (!fs->is_stub()) {
if (playlists->source_use_count (fs) != 0) {
all_sources.insert (fs->path());
} else {
-
+
/* we might not remove this source from disk, because it may be used
by other snapshots, but its not being used in this version
so lets get rid of it now, along with any representative regions
in the region list.
*/
-
+
RegionFactory::remove_regions_using_source (i->second);
- 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 () ) {
+ if (spath == i->second->name()) {
all_sources.erase (j);
break;
}
}
+
+ sources.erase (i);
}
}
}
/* now try to move all unused files into the "dead" directory(ies) */
for (vector<string>::iterator x = unused.begin(); x != unused.end(); ++x) {
- struct stat statbuf;
+ GStatBuf statbuf;
string newpath;
}
- stat ((*x).c_str(), &statbuf);
+ g_stat ((*x).c_str(), &statbuf);
if (::rename ((*x).c_str(), newpath.c_str()) != 0) {
error << string_compose (_("cannot rename unused file source from %1 to %2 (%3)"),
/* see if there an easy to find peakfile for this file, and remove it.
*/
- string base = basename_nosuffix (*x);
+ string base = Glib::path_get_basename (*x);
base += "%A"; /* this is what we add for the channel suffix of all native files,
or for the first channel of embedded files. it will miss
some peakfiles for other channels
*/
- string peakpath = peak_path (base);
+ string peakpath = construct_peak_filepath (base);
if (Glib::file_test (peakpath.c_str(), Glib::FILE_TEST_EXISTS)) {
if (::g_unlink (peakpath.c_str()) != 0) {
}
rep.paths.push_back (*x);
- rep.space += statbuf.st_size;
- }
+ rep.space += statbuf.st_size;
+ }
/* dump the history list */
if (p) {
boost::shared_ptr<Send> s = boost::dynamic_pointer_cast<Send>(p);
boost::shared_ptr<Amp> a = s->amp();
-
+
if (a) {
c = s->amp()->gain_control();
}
return 0;
}
- if (!Config->get_save_history() || Config->get_saved_history_depth() < 0 ||
+ if (!Config->get_save_history() || Config->get_saved_history_depth() < 0 ||
(_history.undo_depth() == 0 && _history.redo_depth() == 0)) {
return 0;
}
}
} else if (p == "click-gain") {
-
+
if (_click_gain) {
_click_gain->set_gain (Config->get_click_gain(), this);
}
* interchange subdirectory
* session file
* session history
-
+
* Backup files are left unchanged and not renamed.
*/
fs->close ();
}
}
-
+
/* pass one: not 100% safe check that the new directory names don't
* already exist ...
*/
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
-
+
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);
-
+
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>::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);
}
newstr = Glib::build_filename (base, legal_name);
cerr << "for " << oldstr << " new dir = " << newstr << endl;
-
- cerr << "Rename " << oldstr << " => " << newstr << endl;
+
+ cerr << "Rename " << oldstr << " => " << newstr << endl;
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;
}
/* Reset path in "session dirs" */
-
+
(*i).path = newstr;
(*i).blocks = 0;
-
+
/* reset primary SessionDirectory object */
-
+
if (first) {
(*_session_dir) = newstr;
new_path = newstr;
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);
+ * (folder/directory) that used to be oldstr to newstr above
+ */
+
+ v.push_back (newstr);
v.push_back (interchange_dir_name);
v.push_back (Glib::path_get_basename (oldstr));
v.push_back (newstr);
v.push_back (interchange_dir_name);
v.push_back (legal_name);
-
+
new_interchange_dir = Glib::build_filename (v);
-
+
cerr << "Rename " << old_interchange_dir << " => " << new_interchange_dir << endl;
-
+
if (::g_rename (old_interchange_dir.c_str(), new_interchange_dir.c_str()) != 0) {
cerr << string_compose (_("renaming %s as %2 failed (%3)"),
old_interchange_dir, new_interchange_dir,
}
/* state file */
-
+
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;
+
+ cerr << "Rename " << oldstr << " => " << newstr << endl;
if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
cerr << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endl;
}
/* history file */
-
+
oldstr = Glib::build_filename (new_path, _current_snapshot_name) + history_suffix;
if (Glib::file_test (oldstr, Glib::FILE_TEST_EXISTS)) {
newstr = Glib::build_filename (new_path, legal_name) + history_suffix;
-
- cerr << "Rename " << oldstr << " => " << newstr << endl;
-
+
+ cerr << "Rename " << oldstr << " => " << newstr << endl;
+
if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
cerr << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endl;
error << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
/* remove old name from recent sessions */
remove_recent_sessions (_path);
_path = new_path;
-
+
/* update file source paths */
-
+
for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
if (fs) {
_current_snapshot_name = new_name;
_name = new_name;
-
+
set_dirty ();
/* save state again to get everything just right */
/* sample rate */
const XMLProperty* prop;
- if ((prop = tree.root()->property (X_("sample-rate"))) != 0) {
+ if ((prop = tree.root()->property (X_("sample-rate"))) != 0) {
sample_rate = atoi (prop->value());
found_sr = true;
}
{
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 {
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.
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);
}
/* 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) {
}
/* 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;
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 ();
+ 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;
/* copy all relevant files. Find each location in session_dirs,
* and copy files from there to target.
*/
-
+
for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
-
+
/* need to clear this because
* find_files_matching_filter() is cumulative
*/
-
+
files.clear ();
-
+
const size_t prefix_len = (*sd).path.size();
-
+
/* Work just on the files within this session dir */
-
+
find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
-
+
/* add dir separator to protect against collisions with
* track names (e.g. track named "audiofiles" or
* "analysis".
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;
continue;
}
#endif
-
+
if (from.find (audiofile_dir_string) != string::npos) {
-
+
/* audio file: only copy if asked */
if (saveas.include_media && saveas.copy_media) {
-
+
string to = make_new_media_path (*i, to_dir, new_folder);
info << "media file copying from " << from << " to " << to << endmsg;
-
+
if (!copy_file (from, to)) {
throw Glib::FileError (Glib::FileError::IO_ERROR,
string_compose(_("\ncopying \"%1\" failed !"), from));
}
}
-
+
/* we found media files inside the session folder */
-
+
internal_file_cnt++;
} else if (from.find (midifile_dir_string) != string::npos) {
*/
if (saveas.include_media) {
-
+
string to = make_new_media_path (*i, to_dir, new_folder);
-
+
info << "media file copying from " << from << " to " << to << endmsg;
-
+
if (!copy_file (from, to)) {
throw Glib::FileError (Glib::FileError::IO_ERROR, "copy failed");
}
}
/* we found media files inside the session folder */
-
+
internal_file_cnt++;
-
+
} else if (from.find (analysis_dir_string) != string::npos) {
/* make sure analysis dir exists in
* new session folder, but we're not
* copying analysis files here, see
- * below
+ * below
*/
-
+
(void) g_mkdir_with_parents (analysis_dir().c_str(), 775);
continue;
-
+
} else {
-
+
/* normal non-media file. Don't copy state, history, etc.
*/
-
+
bool do_copy = true;
-
+
for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
if ((from.length() > (*v).length()) && (from.find (*v) == from.length() - (*v).length())) {
/* end of filename matches extension, do not copy file */
do_copy = false;
break;
- }
+ }
}
if (!saveas.copy_media && from.find (peakfile_suffix) != string::npos) {
*/
do_copy = false;
}
-
+
if (do_copy) {
string to = Glib::build_filename (to_dir, from.substr (prefix_len));
-
+
info << "attempting to make directory/folder " << to << endmsg;
if (g_mkdir_with_parents (Glib::path_get_dirname (to).c_str(), 0755)) {
}
info << "attempting to copy " << from << " to " << to << endmsg;
-
+
if (!copy_file (from, to)) {
throw Glib::FileError (Glib::FileError::IO_ERROR,
string_compose(_("\ncopying \"%1\" failed !"), from));
}
}
}
-
+
/* measure file size even if we're not going to copy so that our Progress
signals are correct, since we included these do-not-copy files
in the computation of the total size and file count.
*/
-
+
GStatBuf gsb;
g_stat (from.c_str(), &gsb);
copied += gsb.st_size;
cnt++;
-
+
double fraction = (double) copied / total_bytes;
-
+
bool keep_going = true;
if (saveas.copy_media) {
* media is not being copied, because
* it will be fast(ish).
*/
-
+
/* tell someone "X percent, file M of N"; M is one-based */
-
+
boost::optional<bool> res = saveas.Progress (fraction, cnt, all);
-
+
if (res) {
keep_going = *res;
}
}
if (saveas.include_media) {
-
+
if (saveas.copy_media) {
#ifndef PLATFORM_WINDOWS
/* There are problems with analysis files on
*
* This is a tricky problem to solve so for
* just don't copy these files. They will be
- * regenerated as-needed anyway, subject to the
+ * regenerated as-needed anyway, subject to the
* existing issue that the filenames will be
* rejected by Windows, which is a separate
* problem (though related).
/* only needed if we are copying media, since the
* analysis data refers to media data
*/
-
+
old = analysis_dir ();
if (Glib::file_test (old, Glib::FILE_TEST_EXISTS)) {
string newdir = Glib::build_filename (to_dir, "analysis");
copy_files (old, newdir);
}
-#endif /* PLATFORM_WINDOWS */
+#endif /* PLATFORM_WINDOWS */
}
}
-
-
+
+
_path = to_dir;
_current_snapshot_name = saveas.new_name;
_name = saveas.new_name;
if (internal_file_cnt) {
for (vector<string>::iterator s = old_search_path[DataType::AUDIO].begin(); s != old_search_path[DataType::AUDIO].end(); ++s) {
ensure_search_path_includes (*s, DataType::AUDIO);
+ cerr << "be sure to include " << *s << " for audio" << endl;
}
/* we do not do this for MIDI because we copy
*/
}
}
-
+
bool was_dirty = dirty ();
save_state ("", false, false, !saveas.include_media);
save_default_options ();
-
+
if (saveas.copy_media && saveas.copy_external) {
if (bring_all_sources_into_session (boost::bind (&Session::save_as_bring_callback, this, _1, _2, _3))) {
throw Glib::FileError (Glib::FileError::NO_SPACE_LEFT, "consolidate failed");
saveas.final_session_folder_name = _path;
store_recent_sessions (_name, _path);
-
+
if (!saveas.switch_to) {
/* switch back to the way things were */
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
session_dirs.push_back (sp);
refresh_disk_space ();
- /* ensure that all existing tracks reset their current capture source paths
+ /* ensure that all existing tracks reset their current capture source paths
*/
reset_write_sources (true, true);
} 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;
}