<menuitem action='SaveAs'/>
#ifndef WINDOWS // can't move open files.
<menuitem action='Rename'/>
+ <menuitem action='SnapshotStay'/>
+ <menuitem action='SnapshotSwitch'/>
#endif
- <menuitem action='Snapshot'/>
<menuitem action='SaveTemplate'/>
<menu name='Metadata' action='Metadata'>
<menuitem action='EditMetadata'/>
#include "rc_option_editor.h"
#include "route_time_axis.h"
#include "route_params_ui.h"
+#include "save_as_dialog.h"
#include "session_dialog.h"
#include "session_metadata_dialog.h"
#include "session_option_editor.h"
clock_signal_connection.disconnect ();
}
+bool
+ARDOUR_UI::save_as_progress_update (float fraction, int64_t cnt, int64_t total, Gtk::Label* label, Gtk::ProgressBar* bar)
+{
+ char buf[256];
+
+ snprintf (buf, sizeof (buf), _("Copied %" PRId64 " of %" PRId64), cnt, total);
+
+ label->set_text (buf);
+ bar->set_fraction (fraction);
+
+ /* process events, redraws, etc. */
+
+ while (gtk_events_pending()) {
+ gtk_main_iteration ();
+ }
+
+ return true; /* continue with save-as */
+}
+
+void
+ARDOUR_UI::save_session_as ()
+{
+ if (!_session) {
+ return;
+ }
+
+ SaveAsDialog sad;
+
+ switch (sad.run()) {
+ case Gtk::RESPONSE_OK:
+ break;
+ default:
+ return;
+ }
+
+ ArdourDialog progress_dialog(_("Save As"), true);
+ Gtk::Label label;
+ Gtk::ProgressBar progress_bar;
+
+ progress_dialog.get_vbox()->pack_start (label);
+ progress_dialog.get_vbox()->pack_start (progress_bar);
+ label.show ();
+ progress_bar.show ();
+
+ Session::SaveAs sa;
+
+ sa.new_parent_folder = sad.new_parent_folder ();
+ sa.new_name = sad.new_name ();
+ sa.switch_to = sad.switch_to();
+ sa.copy_media = sad.copy_media();
+ sa.copy_external = sad.copy_external();
+
+ /* this signal will be emitted from within this, the calling thread,
+ * after every file is copied. It provides information on percentage
+ * complete (in terms of total data to copy), the number of files
+ * copied so far, and the total number to copy.
+ */
+
+ ScopedConnection c;
+
+ sa.Progress.connect_same_thread (c, boost::bind (&ARDOUR_UI::save_as_progress_update, this, _1, _2, _3, &label, &progress_bar));
+
+ progress_dialog.show_all ();
+ progress_dialog.present ();
+
+ if (_session->save_as (sa)) {
+ /* ERROR MESSAGE */
+ MessageDialog msg (string_compose (_("Save As failed: %1"), sa.failure_message));
+ msg.run ();
+ }
+}
+
/** Ask the user for the name of a new snapshot and then take it.
*/
class TearOff;
}
+namespace Gtk {
+ class ProgressBar;
+}
+
class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr
{
private:
guint32 last_key_press_time;
void snapshot_session (bool switch_to_it);
+ bool save_as_progress_update (float fraction, int64_t cnt, int64_t total, Gtk::Label* label, Gtk::ProgressBar* bar);
+ void save_session_as ();
void rename_session ();
void setup_order_hint (AddRouteDialog::InsertAt);
hide_return (sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::export_video), false)));
ActionManager::session_sensitive_actions.push_back (act);
- act = ActionManager::register_action (main_actions, X_("Snapshot"), _("Snapshot..."), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::snapshot_session), false));
+ act = ActionManager::register_action (main_actions, X_("SnapshotStay"), _("Snapshot (& keep working on current version) ..."), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::snapshot_session), false));
ActionManager::session_sensitive_actions.push_back (act);
ActionManager::write_sensitive_actions.push_back (act);
- act = ActionManager::register_action (main_actions, X_("SaveAs"), _("Save As..."), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::snapshot_session), true));
+ act = ActionManager::register_action (main_actions, X_("SnapshotSwitch"), _("Snapshot (& switch to new version) ..."), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::snapshot_session), true));
+ ActionManager::session_sensitive_actions.push_back (act);
+ ActionManager::write_sensitive_actions.push_back (act);
+
+ act = ActionManager::register_action (main_actions, X_("SaveAs"), _("Save As..."), sigc::mem_fun(*this, &ARDOUR_UI::save_session_as));
ActionManager::session_sensitive_actions.push_back (act);
ActionManager::write_sensitive_actions.push_back (act);
--- /dev/null
+/*
+ Copyright (C) 2015 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <gtkmm/stock.h>
+
+#include "ardour/session.h"
+
+#include "save_as_dialog.h"
+
+#include "i18n.h"
+
+using namespace std;
+using namespace Gtk;
+using namespace ARDOUR;
+
+SaveAsDialog::SaveAsDialog ()
+ : ArdourDialog (_("Save As"))
+ , switch_to_button (_("Switch to newly-saved version"))
+ , copy_media_button (_("Copy media to new session"))
+ , copy_external_button (_("Copy external media into new session"))
+{
+ VBox* vbox = get_vbox();
+
+ vbox->set_spacing (6);
+
+ HBox* hbox;
+ Label* label;
+
+ hbox = manage (new HBox);
+ hbox->set_spacing (6);
+ label = manage (new Label (_("Save as session name")));
+ hbox->pack_start (*label, false, false);
+ hbox->pack_start (new_name_entry, true, true);
+ vbox->pack_start (*hbox, false, false);
+
+ hbox = manage (new HBox);
+ hbox->set_spacing (6);
+ label = manage (new Label (_("Parent directory/folder")));
+ hbox->pack_start (*label, false, false);
+ hbox->pack_start (new_parent_folder_selector, true, true);
+ vbox->pack_start (*hbox, false, false);
+
+ vbox->pack_start (switch_to_button, false, false);
+ vbox->pack_start (copy_media_button, false, false);
+ vbox->pack_start (copy_external_button, false, false);
+
+ switch_to_button.set_active (true);
+ copy_media_button.set_active (true);
+
+ vbox->show_all ();
+
+ add_button (Stock::CANCEL, RESPONSE_CANCEL);
+ add_button (Stock::OK, RESPONSE_OK);
+
+ new_parent_folder_selector.set_action (FILE_CHOOSER_ACTION_SELECT_FOLDER);
+ new_parent_folder_selector.set_current_folder (Glib::get_home_dir());
+ new_name_entry.signal_changed().connect (sigc::mem_fun (*this, &SaveAsDialog::name_entry_changed));
+ set_response_sensitive (RESPONSE_OK, false);
+}
+
+void
+SaveAsDialog::name_entry_changed ()
+{
+ if (!new_name_entry.get_text().empty()) {
+ set_response_sensitive (RESPONSE_OK);
+ }
+}
+
+string
+SaveAsDialog::new_parent_folder () const
+{
+ return new_parent_folder_selector.get_current_folder ();
+}
+
+string
+SaveAsDialog::new_name () const
+{
+ return new_name_entry.get_text ();
+}
+
+bool
+SaveAsDialog::switch_to () const
+{
+ return switch_to_button.get_active ();
+}
+
+bool
+SaveAsDialog::copy_media () const
+{
+ return copy_media_button.get_active ();
+}
+
+bool
+SaveAsDialog::copy_external () const
+{
+ return copy_external_button.get_active ();
+}
--- /dev/null
+/*
+ Copyright (C) 2015 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef __ardour_gtk_save_as_dialog_h__
+#define __ardour_gtk_save_as_dialog_h__
+
+#include <gtkmm/entry.h>
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/filechooserbutton.h>
+
+#include "ardour_dialog.h"
+
+class SaveAsDialog : public ArdourDialog
+{
+public:
+ SaveAsDialog ();
+
+ std::string new_parent_folder () const;
+ std::string new_name () const;
+
+ bool switch_to () const;
+ bool copy_media () const;
+ bool copy_external () const;
+
+private:
+ Gtk::CheckButton switch_to_button;
+ Gtk::CheckButton copy_media_button;
+ Gtk::CheckButton copy_external_button;
+ Gtk::FileChooserButton new_parent_folder_selector;
+ Gtk::Entry new_name_entry;
+
+ void name_entry_changed ();
+};
+
+#endif /* __ardour_gtk_tempo_dialog_h__ */
'route_time_axis.cc',
'route_ui.cc',
'ruler_dialog.cc',
+ 'save_as_dialog.cc',
'search_path_option.cc',
'selection.cc',
'selection_memento.cc',
static int get_soundfile_info (string path, SoundFileInfo& _info, string& error_msg);
protected:
+ void close ();
framecnt_t read_unlocked (Sample *dst, framepos_t start, framecnt_t cnt) const;
framecnt_t write_unlocked (Sample *, framecnt_t) { return 0; }
public:
virtual ~FileSource ();
- virtual const std::string& path() const { return _path; }
-
+ const std::string& path() const { return _path; }
+
virtual bool safe_file_extension (const std::string& path) const = 0;
int move_to_trash (const std::string& trash_dir_name);
*/
int rename (const std::string& name);
- virtual void release_descriptor () {}
+ virtual void close () = 0;
-protected:
+ protected:
FileSource (Session& session, DataType type,
const std::string& path,
const std::string& origin,
framecnt_t worst_input_latency () const { return _worst_input_latency; }
framecnt_t worst_track_latency () const { return _worst_track_latency; }
framecnt_t worst_playback_latency () const { return _worst_output_latency + _worst_track_latency; }
+
+ struct SaveAs {
+ std::string new_parent_folder; /* parent folder where new session folder will be created */
+ std::string new_name; /* name of newly saved session */
+ bool switch_to; /* true if we should be working on newly saved session after save-as; false otherwise */
+ bool copy_media; /* true if media files (audio, media, etc) should be copied into newly saved session; false otherwise */
+ bool copy_external; /* true if external media should be consolidated into the newly saved session; false otherwise */
+
+ /* emitted as we make progress. 3 arguments passed to signal
+ * handler:
+ *
+ * 1: percentage complete measured as a fraction (0-1.0) of
+ * total data copying done.
+ * 2: number of files copied so far
+ * 3: total number of files to copy
+ *
+ * Handler should return true for save-as to continue, or false
+ * to stop (and remove all evidence of partial save-as).
+ */
+ PBD::Signal3<bool,float,int64_t,int64_t> Progress;
+
+ /* if save_as() returns non-zero, this string will indicate the reason why.
+ */
+ std::string failure_message;
+ };
+ int save_as (SaveAs&);
int save_state (std::string snapshot_name, bool pending = false, bool switch_to_snapshot = false);
int restore_state (std::string snapshot_name);
int save_template (std::string template_name);
void remove_state (std::string snapshot_name);
void rename_state (std::string old_name, std::string new_name);
void remove_pending_capture_state ();
- int rename (const std::string&);
+ int rename (const std::string&, bool after_copy = false);
bool get_nsm_state () const { return _under_nsm_control; }
void set_nsm_state (bool state) { _under_nsm_control = state; }
bool save_default_options ();
typedef std::map<PBD::ID,boost::shared_ptr<Source> > SourceMap;
private:
+ void reset_write_sources (bool mark_write_complete, bool force = false);
SourceMap sources;
+
private:
int load_sources (const XMLNode& node);
XMLNode& get_sources_as_xml ();
void setup_click_state (const XMLNode*);
void setup_bundles ();
+ void save_as_bring_callback (uint32_t, uint32_t, std::string);
+
static int get_session_info_from_path (XMLTree& state_tree, const std::string& xmlpath);
};
bool clamped_at_unity() const { return false; }
protected:
+ void close() {}
friend class SourceFactory;
SilentFileSource (Session& s, const XMLNode& x, framecnt_t len, float srate)
void prevent_deletion ();
protected:
+ void close ();
void set_path (const std::string& newpath);
void flush_midi (const Lock& lock);
static int get_soundfile_info (const std::string& path, SoundFileInfo& _info, std::string& error_msg);
protected:
+ void close ();
+
void set_path (const std::string& p);
void set_header_timeline_position ();
bool clamped_at_unity() const { return false; }
protected:
+ void close ();
framecnt_t read_unlocked (Sample *dst, framepos_t start, framecnt_t cnt) const;
framecnt_t write_unlocked (Sample */*dst*/, framecnt_t /*cnt*/) { return 0; }
{
}
+void
+CoreAudioSource::close ()
+{
+ af.Close ();
+}
+
int
CoreAudioSource::safe_read (Sample* dst, framepos_t start, framecnt_t cnt, AudioBufferList& abl) const
{
}
found_path = keeppath;
-
ret = true;
out:
void
FileSource::set_path (const std::string& newpath)
{
+ close ();
_path = newpath;
set_within_session_from_path (newpath);
if (_within_session) {
break;
}
} else {
- info << string_compose (_("Track %1 of %2 contained no usable MIDI data"), i, source->file_path()) << endmsg;
+ info << string_compose (_("Track %1 of %2 contained no usable MIDI data"), i, source->num_tracks()) << endmsg;
}
++s; // next source
}
- } catch (...) {
- error << string_compose (_("MIDI file %1 was not readable (no reason available)"), source->file_path()) << endmsg;
+ } catch (exception& e) {
+ error << string_compose (_("MIDI file could not be written (best guess: %1)"), e.what()) << endmsg;
}
if (buf) {
tmp_paths.push_back (sound_path ());
tmp_paths.push_back (midi_path ());
+ tmp_paths.push_back (video_path ());
tmp_paths.push_back (peak_path ());
tmp_paths.push_back (dead_path ());
tmp_paths.push_back (export_path ());
#include <glibmm.h>
#include <glibmm/threads.h>
+#include <glibmm/fileutils.h>
#include <boost/algorithm/string.hpp>
_path = canonical_path(fullpath);
- /* we require _path to end with a dir separator */
-
- if (_path[_path.length()-1] != G_DIR_SEPARATOR) {
- _path += G_DIR_SEPARATOR;
- }
-
/* is it new ? */
_is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
std::string tmp_path(_session_dir->root_path());
tmp_path = Glib::build_filename (tmp_path, legalize_for_path (snapshot_name) + temp_suffix);
- // cerr << "actually writing state to " << xml_path << endl;
-
+ cerr << "actually writing state to " << tmp_path << endl;
+
if (!tree.write (tmp_path)) {
error << string_compose (_("state could not be saved to %1"), tmp_path) << endmsg;
if (g_remove (tmp_path.c_str()) != 0) {
} 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;
return *node;
}
+void
+Session::reset_write_sources (bool mark_write_complete, bool force)
+{
+ boost::shared_ptr<RouteList> rl = routes.reader();
+ for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+ boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+ if (tr) {
+
+ // block state saving
+ _state_of_the_state = StateOfTheState (_state_of_the_state|InCleanup);
+ tr->reset_write_sources(mark_write_complete, force);
+ _state_of_the_state = StateOfTheState (_state_of_the_state & ~InCleanup);
+ }
+ }
+}
+
int
Session::load_sources (const XMLNode& node)
{
}
int
-Session::rename (const std::string& new_name)
+Session::rename (const std::string& new_name, bool after_copy)
{
string legal_name = legalize_for_path (new_name);
- string newpath;
+ string new_path;
string oldstr;
string newstr;
bool first = true;
* already exist ...
*/
- vector<space_and_path> new_session_dirs;
-
- for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
- vector<string> v;
-
- oldstr = (*i).path;
-
- /* 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 (!after_copy) {
- if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
- oldstr = oldstr.substr (0, oldstr.length() - 1);
- }
-
- string base = Glib::path_get_dirname (oldstr);
- string p = Glib::path_get_basename (oldstr);
+ for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+
+ if (first) {
+ /* primary session directory */
+ newstr = _path;
+ first = false;
+ } else {
+ 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);
-
- if (Glib::file_test (newstr, Glib::FILE_TEST_EXISTS)) {
- return -1;
+ string base = Glib::path_get_dirname (oldstr);
+ string p = Glib::path_get_basename (oldstr);
+
+ newstr = Glib::build_filename (base, legal_name);
+ }
+
+ if (Glib::file_test (newstr, Glib::FILE_TEST_EXISTS)) {
+ return -1;
+ }
}
-
- space_and_path sp;
- sp.path = newstr;
- sp.blocks = 0; // not needed
- new_session_dirs.push_back(sp);
}
/* Session dirs */
- for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+ first = false;
+
+ for (vector<space_and_path>::iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+
vector<string> v;
oldstr = (*i).path;
-
+
/* this is a stupid hack because Glib::path_get_dirname() is
* lexical-only, and so passing it /a/b/c/ gives a different
* result than passing it /a/b/c ...
*/
-
+
if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
oldstr = oldstr.substr (0, oldstr.length() - 1);
}
- string base = Glib::path_get_dirname (oldstr);
- string p = Glib::path_get_basename (oldstr);
-
- newstr = Glib::build_filename (base, legal_name);
-
- cerr << "Rename " << oldstr << " => " << newstr << endl;
+ if (first) {
+ newstr = _path;
+ } else {
+ string base = Glib::path_get_dirname (oldstr);
+ newstr = Glib::build_filename (base, legal_name);
+ }
- if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
- error << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
- return 1;
+ if (!after_copy) {
+ cerr << "Rename " << oldstr << " => " << newstr << endl;
+ if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
+ error << string_compose (_("renaming %s as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
+ return 1;
+ }
}
+ /* Reset path in "session dirs" */
+
+ (*i).path = newstr;
+ (*i).blocks = 0;
+
+ /* reset primary SessionDirectory object */
+
if (first) {
(*_session_dir) = newstr;
- newpath = newstr;
- first = 1;
+ new_path = newstr;
+ first = false;
}
- /* directory below interchange */
+ /* now rename directory below session_dir/interchange */
- v.push_back (newstr);
+ string old_interchange_dir;
+ string new_interchange_dir;
+
+ /* use newstr here because we renamed the path that used to be oldstr to newstr above */
+
+ v.push_back (newstr);
v.push_back (interchange_dir_name);
- v.push_back (p);
+ v.push_back (Glib::path_get_basename (oldstr));
- oldstr = Glib::build_filename (v);
+ old_interchange_dir = Glib::build_filename (v);
v.clear ();
v.push_back (newstr);
v.push_back (interchange_dir_name);
v.push_back (legal_name);
-
- newstr = Glib::build_filename (v);
- cerr << "Rename " << oldstr << " => " << newstr << endl;
+ new_interchange_dir = Glib::build_filename (v);
- if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
- error << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
+ cerr << "Rename " << old_interchange_dir << " => " << new_interchange_dir << endl;
+
+ if (::g_rename (old_interchange_dir.c_str(), new_interchange_dir.c_str()) != 0) {
+ error << string_compose (_("renaming %s as %2 failed (%3)"),
+ old_interchange_dir, new_interchange_dir,
+ g_strerror (errno))
+ << endmsg;
return 1;
}
}
- session_dirs = new_session_dirs;
-
/* state file */
- oldstr = Glib::build_filename (newpath, _current_snapshot_name) + statefile_suffix;
- newstr= Glib::build_filename (newpath, legal_name) + statefile_suffix;
+ 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;
}
/* history file */
-
- oldstr = Glib::build_filename (newpath, _current_snapshot_name) + history_suffix;
+ oldstr = Glib::build_filename (new_path, _current_snapshot_name) + history_suffix;
if (Glib::file_test (oldstr, Glib::FILE_TEST_EXISTS)) {
- newstr = Glib::build_filename (newpath, legal_name) + history_suffix;
+ newstr = Glib::build_filename (new_path, legal_name) + history_suffix;
cerr << "Rename " << oldstr << " => " << newstr << endl;
}
}
- /* 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) {
- string p = fs->path ();
- boost::replace_all (p, old_sources_root, _session_dir->sources_root());
- fs->set_path (p);
- SourceFactory::setup_peakfile(i->second, true);
+ if (!after_copy) {
+ /* 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) {
+ string p = fs->path ();
+ boost::replace_all (p, old_sources_root, _session_dir->sources_root());
+ fs->set_path (p);
+ SourceFactory::setup_peakfile(i->second, true);
+ }
}
}
- /* remove old name from recent sessions */
-
- remove_recent_sessions (_path);
-
- _path = newpath;
_current_snapshot_name = new_name;
_name = new_name;
- /* re-add directory separator - reverse hack to oldstr above */
- if (_path[_path.length()-1] != G_DIR_SEPARATOR) {
- _path += G_DIR_SEPARATOR;
- }
-
set_dirty ();
/* save state again to get everything just right */
save_state (_current_snapshot_name);
-
/* add to recent sessions */
store_recent_sessions (new_name, _path);
}
if (fs->within_session()) {
- cerr << "skip " << fs->name() << endl;
continue;
}
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)) {
return ret;
}
+
+static
+bool accept_all_files (string const &, void *)
+{
+ return true;
+}
+
+void
+Session::save_as_bring_callback (uint32_t,uint32_t,string)
+{
+ /* It would be good if this did something useful vis-a-vis save-as, but the arguments doesn't provide the correct information right now to do this.
+ */
+}
+
+static string
+make_new_media_path (string old_path, string new_session_folder, string new_session_path)
+{
+ /* typedir is the "midifiles" or "audiofiles" etc. part of the path. */
+
+ string typedir = Glib::path_get_basename (Glib::path_get_dirname (old_path));
+ vector<string> v;
+ v.push_back (new_session_folder); /* full path */
+ v.push_back (interchange_dir_name);
+ v.push_back (new_session_path); /* just one directory/folder */
+ v.push_back (typedir);
+ v.push_back (Glib::path_get_basename (old_path));
+
+ return Glib::build_filename (v);
+}
+
+int
+Session::save_as (SaveAs& saveas)
+{
+ vector<string> files;
+ string current_folder = Glib::path_get_dirname (_path);
+ string new_folder = legalize_for_path (saveas.new_name);
+ string to_dir = Glib::build_filename (saveas.new_parent_folder, new_folder);
+ int64_t total_bytes = 0;
+ int64_t copied = 0;
+ int64_t cnt = 0;
+ int64_t all = 0;
+ int32_t internal_file_cnt = 0;
+
+ vector<string> do_not_copy_extensions;
+ do_not_copy_extensions.push_back (statefile_suffix);
+ do_not_copy_extensions.push_back (pending_suffix);
+ do_not_copy_extensions.push_back (backup_suffix);
+ do_not_copy_extensions.push_back (temp_suffix);
+ do_not_copy_extensions.push_back (history_suffix);
+
+ /* get total size */
+
+ for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+
+ /* need to clear this because
+ * find_files_matching_filter() is cumulative
+ */
+
+ files.clear ();
+
+ find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
+
+ all += files.size();
+
+ for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
+ GStatBuf gsb;
+ g_stat ((*i).c_str(), &gsb);
+ total_bytes += gsb.st_size;
+ }
+ }
+
+ /* save old values so we can switch back if we are not switching to the new session */
+
+ string old_path = _path;
+ string old_name = _name;
+ string old_snapshot = _current_snapshot_name;
+ string old_sd = _session_dir->root_path();
+ vector<string> old_search_path[DataType::num_types];
+ string old_config_search_path[DataType::num_types];
+
+ old_search_path[DataType::AUDIO] = source_search_path (DataType::AUDIO);
+ old_search_path[DataType::MIDI] = source_search_path (DataType::MIDI);
+ old_config_search_path[DataType::AUDIO] = config.get_audio_search_path ();
+ old_config_search_path[DataType::MIDI] = config.get_midi_search_path ();
+
+ /* switch session directory */
+
+ (*_session_dir) = to_dir;
+
+ /* create new tree */
+
+ if (!_session_dir->create()) {
+ saveas.failure_message = string_compose (_("Cannot create new session folder %1"), to_dir);
+ return -1;
+ }
+
+ try {
+ /* copy all media files. Find each location in
+ * session_dirs, and copy files from there to
+ * target.
+ */
+
+ for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+
+ /* need to clear this because
+ * find_files_matching_filter() is cumulative
+ */
+
+ files.clear ();
+
+ const size_t prefix_len = (*sd).path.size();
+
+ /* Work just on the files within this session dir */
+
+ find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
+
+ /* copy all the files. Handling is different for media files
+ than others because of the *silly* subtree we have below the interchange
+ folder. That really was a bad idea, but I'm not fixing it as part of
+ implementing ::save_as().
+ */
+
+ for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
+
+ std::string from = *i;
+
+ if ((*i).find (interchange_dir_name) != string::npos) {
+
+ /* media file */
+
+ if (saveas.copy_media) {
+
+ string to = make_new_media_path (*i, to_dir, new_folder);
+
+ if (!copy_file (from, to)) {
+ throw Glib::FileError (Glib::FileError::IO_ERROR, "copy failed");
+ }
+ }
+
+ /* we found media files inside the session folder */
+
+ internal_file_cnt++;
+
+ } else {
+
+ /* normal non-media file. Don't copy state, history, etc.
+ */
+
+ bool do_copy = true;
+
+ for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
+ if (((*i).length() > (*v).length()) && ((*i).find (*v) == (*i).length() - (*v).length())) {
+ /* end of filename matches extension, do not copy file */
+ do_copy = false;
+ break;
+ }
+ }
+
+ if (do_copy) {
+ string to = Glib::build_filename (to_dir, (*i).substr (prefix_len));
+
+ if (g_mkdir_with_parents (Glib::path_get_dirname (to).c_str(), 0755)) {
+ throw Glib::FileError (Glib::FileError::IO_ERROR, "cannot create required directory");
+ }
+
+ if (!copy_file (from, to)) {
+ throw Glib::FileError (Glib::FileError::IO_ERROR, "copy failed");
+ }
+ }
+ }
+
+ /* measure file size even if we're not going to copy so that our Progress
+ signals are correct, since we included these do-not-copy files
+ in the computation of the total size and file count.
+ */
+
+ GStatBuf gsb;
+ g_stat ((*i).c_str(), &gsb);
+ copied += gsb.st_size;
+ cnt++;
+
+ double fraction = (double) copied / total_bytes;
+
+ /* tell someone "X percent, file M of N"; M is one-based */
+
+ boost::optional<bool> res = saveas.Progress (fraction, cnt, all);
+ bool keep_going = true;
+
+ if (res) {
+ keep_going = *res;
+ }
+
+ if (!keep_going) {
+ throw Glib::FileError (Glib::FileError::FAILED, "copy cancelled");
+ }
+ }
+
+ }
+
+ _path = to_dir;
+ _current_snapshot_name = saveas.new_name;
+ _name = saveas.new_name;
+
+ if (!saveas.copy_media) {
+
+ /* reset search paths of the new session (which we're pretending to be right now) to
+ include the original session search path, so we can still find all audio.
+ */
+
+ if (internal_file_cnt) {
+ for (vector<string>::iterator s = old_search_path[DataType::AUDIO].begin(); s != old_search_path[DataType::AUDIO].end(); ++s) {
+ ensure_search_path_includes (*s, DataType::AUDIO);
+ }
+
+ for (vector<string>::iterator s = old_search_path[DataType::MIDI].begin(); s != old_search_path[DataType::MIDI].end(); ++s) {
+ ensure_search_path_includes (*s, DataType::MIDI);
+ }
+ }
+ }
+
+ bool was_dirty = dirty ();
+
+ save_state ("", false, false);
+ 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");
+ }
+ }
+
+ if (!saveas.switch_to) {
+
+ /* switch back to the way things were */
+
+ _path = old_path;
+ _name = old_name;
+ _current_snapshot_name = old_snapshot;
+
+ (*_session_dir) = old_sd;
+
+ if (was_dirty) {
+ set_dirty ();
+ }
+
+ if (internal_file_cnt) {
+ /* reset these to their original values */
+ config.set_audio_search_path (old_config_search_path[DataType::AUDIO]);
+ config.set_midi_search_path (old_config_search_path[DataType::MIDI]);
+ }
+
+ } else {
+
+ /* prune session dirs, and update disk space statistics
+ */
+
+ space_and_path sp;
+ sp.path = _path;
+ session_dirs.clear ();
+ session_dirs.push_back (sp);
+ refresh_disk_space ();
+
+ /* ensure that all existing tracks reset their current capture source paths
+ */
+ reset_write_sources (true, true);
+
+ /* the copying above was based on actually discovering files, not just iterating over the sources list.
+ But if we're going to switch to the new (copied) session, we need to change the paths in the sources also.
+ */
+
+ for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+ boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
+
+ if (!fs) {
+ continue;
+ }
+
+ if (fs->within_session()) {
+ string newpath = make_new_media_path (fs->path(), to_dir, new_folder);
+ fs->set_path (newpath);
+ }
+ }
+ }
+
+ } catch (Glib::FileError& e) {
+
+ saveas.failure_message = e.what();
+
+ /* recursively remove all the directories */
+
+ remove_directory (to_dir);
+
+ /* return error */
+
+ return -1;
+
+ } catch (...) {
+
+ saveas.failure_message = _("unknown reason");
+
+ /* recursively remove all the directories */
+
+ remove_directory (to_dir);
+
+ /* return error */
+
+ return -1;
+ }
+
+ return 0;
+}
return 0;
}
+void
+SMFSource::close ()
+{
+ /* nothing to do: file descriptor is never kept open */
+}
+
/** All stamps in audio frames */
framecnt_t
SMFSource::read_unlocked (const Lock& lock,
_model->set_edited(false);
}
- Evoral::SMF::end_write ();
+ Evoral::SMF::end_write (_path);
/* data in the file now, not removable */
ensure_disk_file (lock);
- Evoral::SMF::end_write ();
+ Evoral::SMF::end_write (_path);
/* data in the file means its no longer removable */
mark_nonremovable ();
SMFSource::set_path (const string& p)
{
FileSource::set_path (p);
- SMF::set_path (_path);
}
/** Ensure that this source has some file on disk, even if it's just a SMF header */
throw failed_constructor();
}
} else {
- /* normal mode: do not open the file here - do that in write_unlocked() as needed
+ /* normal mode: do not open the file here - do that in {read,write}_unlocked() as needed
*/
}
}
AudioFileSource::HeaderPositionOffsetChanged.connect_same_thread (header_position_connection, boost::bind (&SndFileSource::handle_header_position_change, this));
}
+void
+SndFileSource::close ()
+{
+ if (_sndfile) {
+ sf_close (_sndfile);
+ _sndfile = 0;
+ }
+}
+
int
SndFileSource::open ()
{
SndFileSource::~SndFileSource ()
{
- if (_sndfile) {
- sf_close (_sndfile);
- _sndfile = 0;
- }
+ close ();
delete _broadcast_info;
delete [] xfade_buf;
}
return cnt;
}
- if (_sndfile == 0) {
- error << string_compose (_("could not allocate file %1 for reading."), _path) << endmsg;
+ if (const_cast<SndFileSource*>(this)->open()) {
+ error << string_compose (_("could not open file %1 for reading."), _path) << endmsg;
return 0;
- }
+ }
if (start > _length) {
delete [] _src_buffer;
}
+void
+SrcFileSource::close ()
+{
+ boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (_source);
+ if (fs) {
+ fs->close ();
+ }
+}
+
framecnt_t
SrcFileSource::read_unlocked (Sample *dst, framepos_t start, framecnt_t cnt) const
{
int create(const std::string& path, int track=1, uint16_t ppqn=19200) THROW_FILE_ERROR;
void close() THROW_FILE_ERROR;
- const std::string& file_path() const { return _file_path; };
-
void seek_to_start() const;
int seek_to_track(int track);
void begin_write();
void append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id);
- void end_write() THROW_FILE_ERROR;
+ void end_write(std::string const &) THROW_FILE_ERROR;
void flush() {};
double round_to_file_precision (double val) const;
-protected:
- void set_path (const std::string& p);
-
private:
- std::string _file_path;
smf_t* _smf;
smf_track_t* _smf_track;
bool _empty; ///< true iff file contains(non-empty) events
smf_delete(_smf);
}
- _file_path = path;
-
- FILE* f = fopen(_file_path.c_str(), "r");
+ FILE* f = fopen(path.c_str(), "r");
if (f == 0) {
return -1;
} else if ((_smf = smf_load(f)) == 0) {
smf_delete(_smf);
}
- _file_path = path;
-
_smf = smf_new();
if (_smf == NULL) {
{
/* put a stub file on disk */
- FILE* f = fopen (_file_path.c_str(), "w+");
+ FILE* f = fopen (path.c_str(), "w+");
if (f == 0) {
return -1;
}
}
void
-SMF::end_write() THROW_FILE_ERROR
+SMF::end_write(string const & path) THROW_FILE_ERROR
{
Glib::Threads::Mutex::Lock lm (_smf_lock);
- FILE* f = fopen (_file_path.c_str(), "w+");
+
+ if (!_smf) {
+ return;
+ }
+
+ FILE* f = fopen (path.c_str(), "w+");
if (f == 0) {
- throw FileError (_file_path);
+ throw FileError (path);
}
if (smf_save(_smf, f) != 0) {
fclose(f);
- throw FileError (_file_path);
+ throw FileError (path);
}
fclose(f);
return round (val * div) / div;
}
-void
-SMF::set_path (const std::string& p)
-{
- _file_path = p;
-}
-
} // namespace Evoral