5dbaaa1c34fac3ed4e27b5981ddb29d1b587bf78
[ardour.git] / session_utils / headless-chicken.cc
1 /*
2     Copyright (C) 2000-2006 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <iostream>
20 #include <cstdlib>
21 #include <getopt.h>
22
23 #include <glibmm.h>
24
25 #include "pbd/file_utils.h"
26 #include "pbd/i18n.h"
27 #include "pbd/stateful.h"
28
29 #include "ardour/region_factory.h"
30 #include "ardour/midi_model.h"
31 #include "ardour/midi_region.h"
32 #include "ardour/midi_source.h"
33 #include "ardour/playlist.h"
34 #include "ardour/region.h"
35 #include "ardour/session_directory.h"
36 #include "ardour/source.h"
37 #include "ardour/source_factory.h"
38 #include "ardour/tempo.h"
39
40 #include "evoral/Note.hpp"
41 #include "evoral/Sequence.hpp"
42
43 #include "common.h"
44
45 using namespace std;
46 using namespace ARDOUR;
47 using namespace SessionUtils;
48
49 void
50 session_fail (Session* session)
51 {
52         SessionUtils::unload_session(session);
53         SessionUtils::cleanup();
54         exit (EXIT_FAILURE);
55 }
56
57 bool
58 write_bbt_source_to_source (boost::shared_ptr<MidiSource>  bbt_source, boost::shared_ptr<MidiSource> source,
59                                  const Glib::Threads::Mutex::Lock& source_lock, const double session_offset)
60 {
61         assert (source->empty());
62
63         const bool old_percussive = bbt_source->model()->percussive();
64
65         bbt_source->model()->set_percussive (false);
66
67         source->mark_streaming_midi_write_started (source_lock, bbt_source->model()->note_mode());
68
69         TempoMap& map (source->session().tempo_map());
70
71         for (Evoral::Sequence<MidiModel::TimeType>::const_iterator i = bbt_source->model()->begin(MidiModel::TimeType(), true); i != bbt_source->model()->end(); ++i) {
72                 const double new_time = map.quarter_note_at_beat ((*i).time().to_double() + map.beat_at_pulse (session_offset)) - (session_offset * 4.0);
73                 Evoral::Event<Evoral::Beats> new_ev (*i, true);
74                 new_ev.set_time (Evoral::Beats (new_time));
75                 source->append_event_beats (source_lock, new_ev);
76         }
77
78         bbt_source->model()->set_percussive (old_percussive);
79         source->mark_streaming_write_completed (source_lock);
80
81         return true;
82 }
83
84 boost::shared_ptr<MidiSource>
85 ensure_per_region_source (Session* session, string newsrc_path, boost::shared_ptr<MidiRegion> region)
86 {
87         boost::shared_ptr<MidiSource> newsrc;
88
89         /* create a new source if none exists and write corrected events to it.
90            if file exists, assume that it is correct.
91         */
92         if (Glib::file_test (newsrc_path, Glib::FILE_TEST_EXISTS)) {
93                 Source::Flag flags =  Source::Flag (Source::Writable | Source::CanRename);
94                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
95                         SourceFactory::createExternal(DataType::MIDI, *session,
96                                                       newsrc_path, 1, flags));
97                 if (!newsrc) {
98                         cout << UTILNAME << "An error occurred creating external source from " << newsrc_path << " exiting." << endl;
99                         session_fail (session);
100                 }
101
102                 /* hack flags */
103                 XMLNode* node = new XMLNode (newsrc->get_state());
104
105                 if (node->property ("flags") != 0) {
106                         node->property ("flags")->set_value (enum_2_string (flags));
107                 }
108
109                 newsrc->set_state (*node, PBD::Stateful::loading_state_version);
110
111                 cout << UTILNAME << ": Using existing midi source file " << newsrc_path << endl;
112                 cout << "for region : " << region->name() << endl;
113
114         } else {
115                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
116                         SourceFactory::createWritable(DataType::MIDI, *session,
117                                                       newsrc_path, false, session->frame_rate()));
118
119                 if (!newsrc) {
120                         cout << UTILNAME << "An error occurred creating writeable source " << newsrc_path << " exiting." << endl;
121                         session_fail (session);
122                 }
123
124                 if (!newsrc->empty()) {
125                         cout << UTILNAME << "An error occurred/ " << newsrc->name() << " is not empty. exiting." << endl;
126                         session_fail (session);
127                 }
128
129                 Source::Lock newsrc_lock (newsrc->mutex());
130
131                 write_bbt_source_to_source (region->midi_source(0), newsrc, newsrc_lock, region->pulse() - (region->start_beats().to_double() / 4.0));
132
133                 cout << UTILNAME << ": Created new midi source file " << newsrc_path << endl;
134                 cout << "for region : " <<  region->name() << endl;
135
136         }
137
138         return newsrc;
139 }
140
141 boost::shared_ptr<MidiSource>
142 ensure_per_source_source (Session* session, string newsrc_path, boost::shared_ptr<MidiRegion> region)
143 {
144         boost::shared_ptr<MidiSource> newsrc;
145
146         /* create a new source if none exists and write corrected events to it. */
147         if (Glib::file_test (newsrc_path, Glib::FILE_TEST_EXISTS)) {
148                 /* flags are ignored for external MIDI source */
149                 Source::Flag flags =  Source::Flag (Source::Writable | Source::CanRename);
150
151                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
152                         SourceFactory::createExternal(DataType::MIDI, *session,
153                                                       newsrc_path, 1, flags));
154
155                 if (!newsrc) {
156                         cout << UTILNAME << "An error occurred creating external source from " << newsrc_path << " exiting." << endl;
157                         session_fail (session);
158                 }
159
160                 cout << UTILNAME << ": Using existing midi source file " << newsrc_path << endl;
161                 cout << "for source : " <<  region->midi_source(0)->name() << endl;
162         } else {
163
164                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
165                         SourceFactory::createWritable(DataType::MIDI, *session,
166                                                       newsrc_path, false, session->frame_rate()));
167                 if (!newsrc) {
168                         cout << UTILNAME << "An error occurred creating writeable source " << newsrc_path << " exiting." << endl;
169                         session_fail (session);
170                 }
171
172                 if (!newsrc->empty()) {
173                         cout << UTILNAME << "An error occurred/ " << newsrc->name() << " is not empty. exiting." << endl;
174                         session_fail (session);
175                 }
176
177                 Source::Lock newsrc_lock (newsrc->mutex());
178
179                 write_bbt_source_to_source (region->midi_source(0), newsrc, newsrc_lock, region->pulse() - (region->start_beats().to_double() / 4.0));
180
181                 cout << UTILNAME << ": Created new midi source file " << newsrc_path << endl;
182                 cout << "for source : " <<  region->midi_source(0)->name() << endl;
183
184         }
185
186         return newsrc;
187 }
188
189 void
190 reset_start_and_length (Session* session, boost::shared_ptr<MidiRegion> region)
191 {
192         /* set start_beats & length_beats to quarter note value */
193         TempoMap& map (session->tempo_map());
194
195         region->set_start_beats (Evoral::Beats ((map.pulse_at_beat (region->beat())
196                                                  - map.pulse_at_beat (region->beat() - region->start_beats().to_double())) * 4.0));
197
198         region->set_length_beats (Evoral::Beats ((map.pulse_at_beat (region->beat() + region->length_beats().to_double())
199                                                   - map.pulse_at_beat (region->beat())) * 4.0));
200
201         cout << UTILNAME << ": Reset start and length beats for region : " << region->name() << endl;
202 }
203
204 bool
205 apply_one_source_per_region_fix (Session* session)
206 {
207         const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
208
209         if (!region_map.size()) {
210                 return false;
211         }
212
213         /* for every midi region, ensure a new source and switch to it. */
214         for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
215                 boost::shared_ptr<MidiRegion> mr = 0;
216
217                 if ((mr = boost::dynamic_pointer_cast<MidiRegion>((*i).second)) != 0) {
218                         reset_start_and_length (session, mr);
219                         string newsrc_filename = mr->name() +  "-a54-compat.mid";
220                         string newsrc_path = Glib::build_filename (session->session_directory().midi_path(), newsrc_filename);
221                         boost::shared_ptr<MidiSource> newsrc = ensure_per_region_source (session, newsrc_path, mr);
222                         mr->clobber_sources (newsrc);
223                 }
224         }
225
226         return true;
227 }
228
229 bool
230 apply_one_source_per_source_fix (Session* session)
231 {
232         const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
233
234         if (!region_map.size()) {
235                 return false;
236         }
237
238         map<PBD::ID, boost::shared_ptr<MidiSource> > old_source_to_new;
239         /* for every midi region, ensure a converted source exists. */
240         for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
241                 boost::shared_ptr<MidiRegion> mr = 0;
242                 map<PBD::ID, boost::shared_ptr<MidiSource> >::iterator src_it;
243
244                 if ((mr = boost::dynamic_pointer_cast<MidiRegion>((*i).second)) != 0) {
245                         reset_start_and_length (session, mr);
246
247                         if ((src_it = old_source_to_new.find (mr->midi_source()->id())) == old_source_to_new.end()) {
248                                 string newsrc_filename = mr->source()->name() +  "-a54-compat.mid";
249                                 string newsrc_path = Glib::build_filename (session->session_directory().midi_path(), newsrc_filename);
250
251                                 boost::shared_ptr<MidiSource> newsrc = ensure_per_source_source (session, newsrc_path, mr);
252
253                                 old_source_to_new.insert (make_pair (mr->midi_source()->id(), newsrc));
254
255                                 mr->midi_source(0)->set_name (newsrc->name());
256                         }
257                 }
258         }
259
260         /* remove new sources from the session. current snapshot is saved.*/
261         cout << UTILNAME << ": clearing new sources." << endl;
262
263         for (map<PBD::ID, boost::shared_ptr<MidiSource> >::iterator i = old_source_to_new.begin(); i != old_source_to_new.end(); ++i) {
264                 session->remove_source (boost::weak_ptr<MidiSource> ((*i).second));
265         }
266
267         return true;
268 }
269
270 static void usage (int status) {
271         // help2man compatible format (standard GNU help-text)
272         printf (UTILNAME " - convert an ardour session with 5.0 - 5.3 midi sources to be compatible with 5.4.\n\n");
273         printf ("Usage: " UTILNAME " [ OPTIONS ] <session-dir> <snapshot-name>\n\n");
274         printf ("Options:\n\
275   -h, --help                    display this help and exit\n\
276   -f, --force                   override detection of affected sessions\n\
277   -o, --output <snapshot-name>  output session snapshot name (without file suffix)\n\
278   -V, --version                 print version information and exit\n\
279 \n");
280         printf ("\n\
281 This Ardour-specific utility provides an upgrade path for sessions created or modified with Ardour versions 5.0 - 5.3.\n\
282 It creates a 5.4-compatible snapshot from affected Ardour session files.\n\
283 Affected versions (5.0 - 5.3 inclusive) contain a bug which caused some MIDI region properties and contents\n\
284 to be stored incorrectly (see more below).\n\n\
285 The utility will first determine whether or not a session requires any changes for 5.4 compatibility.\n\
286 If a session is determined to be affected by the bug, the program will take one of two approaches to correcting the problem.\n\n\
287 The first is to write a new MIDI source file for every existing MIDI source in the supplied snapshot.\n\
288 In the second approach, each MIDI region have its source converted and placed in the session midifiles directory\n\
289 as a new source (one source file per region).\n\
290 The second method is only offered if the first approach cannot logically ensure that the results would match the input snapshot.\n\
291 Using the first method even if the second method is offered will usually match the input exactly (partly due to a characteristic of the bug).\n\n\
292 Both methods update MIDI region properties and save a new snapshot in the supplied session-dir, optionally using a supplied snapshot name (-o).\n\
293 The new snapshot may be used on Ardour-5.4.\n\n\
294 Running this utility should not alter any existing files, but it is recommended that you run it on a backup of the session directory.\n\n\
295 EXAMPLE:\n\
296 ardour5-headless-chicken -o bantam ~/studio/leghorn leghorn\n\
297 will create a new snapshot file ~/studio/leghorn/bantam.ardour from ~/studio/leghorn/leghorn.ardour\n\
298 Converted midi sources will be created in ~/studio/leghorn/interchange/leghorn/midifiles/\n\
299 If the output option (-o) is omitted, the string \"-a54-compat\" will be appended to the supplied snapshot name.\n\n\
300 About the Bug\n\
301 If a session from affected versions used MIDI regions and a meter note divisor was set to anything but quarter notes,\n\
302 the source smf files would contain events at a PPQN value derived from BBT beats (using meter note divisor) rather than quarter-note beatss.\n\
303 The region start and length offsets would also be stored incorrectly.\n\
304 If a MIDI session only contains quarter note meter divisors, it will be unaffected.\n\
305 \n");
306
307         printf ("Report bugs to <http://tracker.ardour.org/>\n"
308                 "Website: <http://ardour.org/>\n");
309         ::exit (status);
310 }
311
312 int main (int argc, char* argv[])
313 {
314         string outfile;
315         bool force = false;
316
317         const char *optstring = "hfo:r:V";
318
319         const struct option longopts[] = {
320                 { "help",       0, 0, 'h' },
321                 { "force",      0, 0, 'f' },
322                 { "output",     1, 0, 'o' },
323                 { "version",    0, 0, 'V' },
324         };
325
326         int c = 0;
327         while (EOF != (c = getopt_long (argc, argv,
328                                         optstring, longopts, (int *) 0))) {
329                 switch (c) {
330
331                 case 'f':
332                         force = true;
333                         break;
334
335                 case 'o':
336                         outfile = optarg;
337                         if (outfile.empty()) {
338                                 usage (0);
339                         }
340                         break;
341
342                 case 'V':
343                         printf ("ardour-utils version %s\n\n", VERSIONSTRING);
344                         printf ("Copyright (C) GPL 2015 Robin Gareus <robin@gareus.org>\n");
345                         exit (0);
346                         break;
347
348                 case 'h':
349                         usage (0);
350                         break;
351
352                 default:
353                         usage (EXIT_FAILURE);
354                         break;
355                 }
356         }
357
358         if (optind + 2 > argc) {
359                 usage (EXIT_FAILURE);
360         }
361
362         SessionDirectory* session_dir = new SessionDirectory (argv[optind]);
363         string snapshot_name (argv[optind+1]);
364         string statefile_suffix (X_(".ardour"));
365         string pending_suffix (X_(".pending"));
366
367         XMLTree* state_tree;
368
369         string xmlpath(argv[optind]);
370         string out_snapshot_name;
371
372         if (!outfile.empty()) {
373                 string file_test_path = Glib::build_filename (argv[optind], outfile + statefile_suffix);
374                 if (Glib::file_test (file_test_path, Glib::FILE_TEST_EXISTS)) {
375                         cout << UTILNAME << ": session file " << file_test_path << " already exists!" << endl;
376                         ::exit (EXIT_FAILURE);
377                 }
378                 out_snapshot_name = outfile;
379         } else {
380                 string file_test_path = Glib::build_filename (argv[optind], snapshot_name + "-a54-compat" + statefile_suffix);
381                 if (Glib::file_test (file_test_path, Glib::FILE_TEST_EXISTS)) {
382                         cout << UTILNAME << ": session file " << file_test_path << " already exists!" << endl;
383                         ::exit (EXIT_FAILURE);
384                 }
385                 out_snapshot_name = snapshot_name + "-a54-compat";
386         }
387
388         xmlpath = Glib::build_filename (xmlpath, legalize_for_path (snapshot_name) + pending_suffix);
389
390         if (Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
391
392                 /* there is pending state from a crashed capture attempt */
393                 cout << UTILNAME << ": There seems to be pending state for snapshot : " << snapshot_name << endl;
394
395         }
396
397         xmlpath = Glib::build_filename (argv[optind], argv[optind+1]);
398
399         if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
400                 xmlpath = Glib::build_filename (argv[optind], legalize_for_path (argv[optind+1]) + ".ardour");
401                 if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
402                         cout << UTILNAME << ": session file " << xmlpath << " doesn't exist!" << endl;
403                         ::exit (EXIT_FAILURE);
404                 }
405         }
406
407         state_tree = new XMLTree;
408
409         bool writable = PBD::exists_and_writable (xmlpath) && PBD::exists_and_writable(Glib::path_get_dirname(xmlpath));
410
411         if (!writable) {
412                 cout << UTILNAME << ": Error : The session directory must exist and be writable." << endl;
413                 return -1;
414         }
415
416         if (!state_tree->read (xmlpath)) {
417                 cout << UTILNAME << ": Could not understand session file " << xmlpath << endl;
418                 delete state_tree;
419                 state_tree = 0;
420                 ::exit (EXIT_FAILURE);
421         }
422
423         XMLNode const & root (*state_tree->root());
424
425         if (root.name() != X_("Session")) {
426                 cout << UTILNAME << ": Session file " << xmlpath<< " is not a session" << endl;
427                 delete state_tree;
428                 state_tree = 0;
429                 ::exit (EXIT_FAILURE);
430         }
431
432         XMLProperty const * prop;
433
434         if ((prop = root.property ("version")) == 0) {
435                 /* no version implies very old version of Ardour */
436                 cout << UTILNAME << ": The session " << snapshot_name << " has no version or is too old to be affected. exiting." << endl;
437                 ::exit (EXIT_FAILURE);
438         } else {
439                 if (prop->value().find ('.') != string::npos) {
440                         /* old school version format */
441                         cout << UTILNAME << ": The session " << snapshot_name << " is too old to be affected. exiting." << endl;
442                         ::exit (EXIT_FAILURE);
443                 } else {
444                         PBD::Stateful::loading_state_version = atoi (prop->value().c_str());
445                 }
446         }
447
448         cout <<  UTILNAME << ": Checking snapshot : " << snapshot_name << " in directory : " << session_dir->root_path() << endl;
449
450         bool midi_regions_use_bbt_beats = false;
451
452         if (PBD::Stateful::loading_state_version == 3002 && writable) {
453                 XMLNode* child;
454                 if ((child = find_named_node (root, "ProgramVersion")) != 0) {
455                         if ((prop = child->property ("modified-with")) != 0) {
456                                 string modified_with = prop->value ();
457
458                                 const double modified_with_version = atof (modified_with.substr ( modified_with.find(" ", 0) + 1, string::npos).c_str());
459                                 const int modified_with_revision = atoi (modified_with.substr (modified_with.find("-", 0) + 1, string::npos).c_str());
460
461                                 if (modified_with_version <= 5.3 && !(modified_with_version == 5.3 && modified_with_revision >= 42)) {
462                                         midi_regions_use_bbt_beats = true;
463                                 }
464                         }
465                 }
466         }
467
468         XMLNode* tm_node;
469         bool all_metrum_divisors_are_quarters = true;
470         list<double> divisor_list;
471
472         if ((tm_node = find_named_node (root, "TempoMap")) != 0) {
473                 XMLNodeList metrum;
474                 XMLNodeConstIterator niter;
475                 metrum = tm_node->children();
476                 for (niter = metrum.begin(); niter != metrum.end(); ++niter) {
477                         XMLNode* child = *niter;
478
479                         if (child->name() == MeterSection::xml_state_node_name && (prop = child->property ("note-type")) != 0) {
480                                 double note_type;
481
482                                 if (sscanf (prop->value().c_str(), "%lf", &note_type) ==1) {
483
484                                         if (note_type != 4.0) {
485                                                 all_metrum_divisors_are_quarters = false;
486                                         }
487
488                                         divisor_list.push_back (note_type);
489                                 }
490                         }
491                 }
492         } else {
493                 cout << UTILNAME << ": Session file " <<  xmlpath << " has no TempoMap node. exiting." << endl;
494                 ::exit (EXIT_FAILURE);
495         }
496
497         if (all_metrum_divisors_are_quarters && !force) {
498                 cout << UTILNAME << ": The session " << snapshot_name << " is clear for use in 5.4 (all divisors are quarters). Use -f to override." << endl;
499                 ::exit (EXIT_FAILURE);
500         }
501
502         /* check for multiple note divisors. if there is only one, we can create one file per source. */
503         bool one_source_file_per_source = false;
504         divisor_list.unique();
505
506         if (divisor_list.size() == 1) {
507                 cout  << UTILNAME << ": Snapshot " << snapshot_name << " will be converted using one new file per source." << endl;
508                 cout  << "To continue with per-source conversion enter s. q to quit." << endl;
509
510                 while (1) {
511                         cout  << "[s/q]" << endl;
512
513                         string input;
514                         getline (cin, input);
515
516                         if (input == "s") {
517                                 break;
518                         }
519
520                         if (input == "q") {
521                                 exit (EXIT_SUCCESS);
522                                 break;
523                         }
524                 }
525
526                 one_source_file_per_source = true;
527         } else {
528
529                 cout  << UTILNAME << ": Snapshot " << snapshot_name << " contains multiple meter note divisors." << endl;
530                 cout  << "per-region source conversion ensures that the output snapshot will be identical to the original," << endl;
531                 cout  << "however regions in the new snapshot will no longer share sources." << endl;
532                 cout  << "In many (but not all) cases per-source conversion will work equally well." << endl;
533                 cout  << "It is recommended that you test a snapshot created with the per-source method before using per-region conversion." << endl;
534                 cout  << "To continue with per-region conversion enter r. For per-source conversion, enter s. q to quit." << endl;
535
536                 while (1) {
537                         cout  << "[r/s/q]" << endl;
538
539                         string input;
540                         getline (cin, input);
541
542                         if (input == "s") {
543                                 one_source_file_per_source = true;
544                                 break;
545                         }
546
547                         if (input == "r") {
548                                 break;
549                         }
550
551                         if (input == "q") {
552                                 exit (EXIT_SUCCESS);
553                                 break;
554                         }
555                 }
556         }
557
558         if (midi_regions_use_bbt_beats || force) {
559
560                 if (force) {
561                         cout << UTILNAME << ": Forced update of snapshot : " << snapshot_name << endl;
562                 }
563
564                 SessionUtils::init();
565                 Session* s = 0;
566
567                 cout <<  UTILNAME << ": Loading snapshot." << endl;
568
569                 s = SessionUtils::load_session (argv[optind], argv[optind+1]);
570
571                 /* save new snapshot and prevent alteration of the original by switching to it.
572                    we know these files don't yet exist.
573                 */
574                 if (s->save_state (out_snapshot_name, false, true)) {
575                         cout << UTILNAME << ": Could not save new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
576
577                         session_fail (s);
578                 }
579
580                 cout << UTILNAME << ": Saved new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
581
582                 if (one_source_file_per_source) {
583                         cout << UTILNAME << ": Will create one MIDI file per source." << endl;
584
585                         if (!apply_one_source_per_source_fix (s)) {
586                                 cout << UTILNAME << ": The snapshot " << snapshot_name << " is clear for use in 5.4 (no midi regions). exiting." << endl;
587                                 session_fail (s);
588                         }
589                 } else {
590                         cout << UTILNAME << ": Will create one MIDI file per midi region." << endl;
591
592                         if (!apply_one_source_per_region_fix (s)) {
593                                 cout << UTILNAME << ": The snapshot " << snapshot_name << " is clear for use in 5.4 (no midi regions). exiting."  << endl;
594                                 session_fail (s);
595                         }
596
597                         if (s->save_state (out_snapshot_name, false, true)) {
598                                 cout << UTILNAME << ": Could not save snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
599                                 session_fail (s);
600                         }
601                         cout << UTILNAME << ": Saved new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
602                 }
603
604                 SessionUtils::unload_session(s);
605                 SessionUtils::cleanup();
606                 cout << UTILNAME << ": Snapshot " << out_snapshot_name << " is ready for use in 5.4" << endl;
607         } else {
608                 cout << UTILNAME << ": The snapshot " << snapshot_name << " doesn't require any change for use in 5.4. Use -f to override." << endl;
609                 ::exit (EXIT_FAILURE);
610         }
611
612         return 0;
613 }