Improved ExportProfileManager error handling, and added some missing (?) initialization
[ardour.git] / libs / ardour / export_profile_manager.cc
1 /*
2     Copyright (C) 2008 Paul Davis
3     Author: Sakari Bergen
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <ardour/export_profile_manager.h>
22
23 #include <cassert>
24 #include <stdexcept>
25
26 #include <glibmm/fileutils.h>
27
28 #include <pbd/enumwriter.h>
29 #include <pbd/xml++.h>
30 #include <pbd/convert.h>
31
32 #include <ardour/export_failed.h>
33 #include <ardour/export_format_specification.h>
34 #include <ardour/export_timespan.h>
35 #include <ardour/export_channel_configuration.h>
36 #include <ardour/export_filename.h>
37 #include <ardour/export_preset.h>
38 #include <ardour/export_handler.h>
39 #include <ardour/filename_extensions.h>
40 #include <ardour/session.h>
41
42 #include "i18n.h"
43
44 using namespace PBD;
45
46 namespace ARDOUR
47 {
48
49 ExportProfileManager::ExportProfileManager (Session & s) :
50   handler (s.get_export_handler()),
51   session (s),
52
53   session_range (new Location ()),
54   ranges (new LocationList ()),
55
56   format_list (new FormatList ())
57 {
58
59         /* Initialize path variables */
60
61         sys::path path;
62
63         export_config_dir = user_config_directory();
64         export_config_dir /= "export";
65         search_path += export_config_dir;
66         
67         path = ardour_search_path().to_string();
68         path /= "export";
69         search_path += path;
70         
71         path = system_config_search_path().to_string();
72         path /= "export";
73         search_path += path;
74         
75         /* create export config directory if necessary */
76
77         if (!sys::exists (export_config_dir)) {
78                 sys::create_directory (export_config_dir);
79         }
80         
81         load_presets ();
82         load_formats ();
83         
84         /* Initialize all lists with an empty config */
85         
86         XMLNodeList dummy;
87         init_timespans (dummy);
88         init_channel_configs (dummy);
89         init_formats (dummy);
90         init_filenames (dummy);
91 }
92
93 ExportProfileManager::~ExportProfileManager ()
94 {
95         XMLNode * instant_xml (new XMLNode ("ExportProfile"));
96         serialize_profile (*instant_xml);
97         session.add_instant_xml (*instant_xml, false);
98 }
99
100 void
101 ExportProfileManager::load_profile ()
102 {
103         XMLNode * instant_node = session.instant_xml ("ExportProfile");
104         if (instant_node) {
105                 set_state (*instant_node);
106         } else {
107                 XMLNode empty_node ("ExportProfile");
108                 set_state (empty_node);
109         }
110 }
111
112 void
113 ExportProfileManager::prepare_for_export ()
114 {
115         ChannelConfigPtr channel_config = channel_configs.front()->config;
116         TimespanListPtr ts_list = timespans.front()->timespans;
117         
118         FormatStateList::const_iterator format_it;
119         FilenameStateList::const_iterator filename_it;
120         
121         for (TimespanList::iterator ts_it = ts_list->begin(); ts_it != ts_list->end(); ++ts_it) {
122                 for (format_it = formats.begin(), filename_it = filenames.begin();
123                      format_it != formats.end() && filename_it != filenames.end();
124                      ++format_it, ++filename_it) {
125                 
126 //                      filename->include_timespan = (ts_list->size() > 1); Disabled for now...
127                         handler->add_export_config (*ts_it, channel_config, (*format_it)->format, (*filename_it)->filename);
128                 }
129         }
130 }
131
132 bool
133 ExportProfileManager::load_preset (PresetPtr preset)
134 {
135         bool ok = true;
136
137         current_preset = preset;
138         if (!preset) { return false; }
139
140         XMLNode const * state;
141         if ((state = preset->get_local_state())) {
142                 set_local_state (*state);
143         } else { ok = false; }
144         
145         if ((state = preset->get_global_state())) {
146                 if (!set_global_state (*state)) {
147                         ok = false;
148                 }
149         } else { ok = false; }
150         
151         return ok;
152 }
153
154 void
155 ExportProfileManager::load_presets ()
156 {
157         vector<sys::path> found = find_file (string_compose (X_("*%1"),export_preset_suffix));
158
159         for (vector<sys::path>::iterator it = found.begin(); it != found.end(); ++it) {
160                 load_preset_from_disk (*it);
161         }
162 }
163
164 ExportProfileManager::PresetPtr
165 ExportProfileManager::save_preset (string const & name)
166 {
167         if (!current_preset) {
168                 string filename = export_config_dir.to_string() + "/" + name + export_preset_suffix;
169                 current_preset.reset (new ExportPreset (filename, session));
170                 preset_list.push_back (current_preset);
171         }
172         
173         XMLNode * global_preset = new XMLNode ("ExportPreset");
174         XMLNode * local_preset = new XMLNode ("ExportPreset");
175
176         serialize_global_profile (*global_preset);
177         serialize_local_profile (*local_preset);
178
179         current_preset->set_name (name);
180         current_preset->set_global_state (*global_preset);
181         current_preset->set_local_state (*local_preset);
182         
183         current_preset->save();
184         
185         return current_preset;
186 }
187
188 void
189 ExportProfileManager::remove_preset ()
190 {
191         if (!current_preset) { return; }
192
193         for (PresetList::iterator it = preset_list.begin(); it != preset_list.end(); ++it) {
194                 if (*it == current_preset) {
195                         preset_list.erase (it);
196                         break;
197                 }
198         }
199
200         FileMap::iterator it = preset_file_map.find (current_preset->id());
201         if (it != preset_file_map.end()) {
202                 sys::remove (it->second);
203                 preset_file_map.erase (it);
204         }
205         
206         current_preset->remove_local();
207         current_preset.reset();
208 }
209
210 void
211 ExportProfileManager::load_preset_from_disk (PBD::sys::path const & path)
212 {
213         PresetPtr preset (new ExportPreset (path.to_string(), session));
214         preset_list.push_back (preset);
215         
216         /* Handle id to filename mapping */
217         
218         FilePair pair (preset->id(), path);
219         preset_file_map.insert (pair);
220 }
221
222 bool
223 ExportProfileManager::set_state (XMLNode const & root)
224 {
225         return set_global_state (root) && set_local_state (root);
226 }
227
228 bool
229 ExportProfileManager::set_global_state (XMLNode const & root)
230 {
231         return init_filenames (root.children ("ExportFilename")) &&
232                init_formats (root.children ("ExportFormat"));
233 }
234
235 bool
236 ExportProfileManager::set_local_state (XMLNode const & root)
237 {
238         return init_timespans (root.children ("ExportTimespan")) &&
239                init_channel_configs (root.children ("ExportChannelConfiguration"));
240 }
241
242 void
243 ExportProfileManager::serialize_profile (XMLNode & root)
244 {
245         serialize_local_profile (root);
246         serialize_global_profile (root);
247 }
248
249 void
250 ExportProfileManager::serialize_global_profile (XMLNode & root)
251 {
252         for (FormatStateList::iterator it = formats.begin(); it != formats.end(); ++it) {
253                 root.add_child_nocopy (serialize_format (*it));
254         }
255
256         for (FilenameStateList::iterator it = filenames.begin(); it != filenames.end(); ++it) {
257                 root.add_child_nocopy ((*it)->filename->get_state());
258         }
259 }
260
261 void
262 ExportProfileManager::serialize_local_profile (XMLNode & root)
263 {
264         for (TimespanStateList::iterator it = timespans.begin(); it != timespans.end(); ++it) {
265                 root.add_child_nocopy (serialize_timespan (*it));
266         }
267         
268         for (ChannelConfigStateList::iterator it = channel_configs.begin(); it != channel_configs.end(); ++it) {
269                 root.add_child_nocopy ((*it)->config->get_state());
270         }
271 }
272
273 std::vector<sys::path>
274 ExportProfileManager::find_file (std::string const & pattern)
275 {
276         vector<sys::path> found;
277
278         Glib::PatternSpec pattern_spec (pattern);
279         find_matching_files_in_search_path (search_path, pattern_spec, found);
280
281         return found;
282 }
283
284 void
285 ExportProfileManager::set_selection_range (nframes_t start, nframes_t end)
286 {
287
288         if (start || end) {
289                 selection_range.reset (new Location());
290                 selection_range->set_name (_("Selection"));
291                 selection_range->set (start, end);
292         } else {
293                 selection_range.reset();
294         }
295         
296         for (TimespanStateList::iterator it = timespans.begin(); it != timespans.end(); ++it) {
297                 (*it)->selection_range = selection_range;
298         }
299 }
300
301 bool
302 ExportProfileManager::init_timespans (XMLNodeList nodes)
303 {
304         timespans.clear ();
305         update_ranges ();
306         
307         bool ok = true;
308         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
309                 TimespanStatePtr span = deserialize_timespan (**it);
310                 if (span) {
311                         timespans.push_back (span);
312                 } else { ok = false; }
313         }
314         
315         if (timespans.empty()) {
316                 TimespanStatePtr timespan (new TimespanState (session_range, selection_range, ranges));
317                 timespans.push_back (timespan);
318                 return false;
319         }
320         
321         return ok;
322 }
323
324 ExportProfileManager::TimespanStatePtr
325 ExportProfileManager::deserialize_timespan (XMLNode & root)
326 {
327         TimespanStatePtr state (new TimespanState (session_range, selection_range, ranges));
328         XMLProperty const * prop;
329         
330         XMLNodeList spans = root.children ("Range");
331         for (XMLNodeList::iterator node_it = spans.begin(); node_it != spans.end(); ++node_it) {
332                 
333                 prop = (*node_it)->property ("id");
334                 if (!prop) { continue; }
335                 ustring id = prop->value();
336         
337                 for (LocationList::iterator it = ranges->begin(); it != ranges->end(); ++it) {
338                         if ((!id.compare ("session") && *it == session_range.get()) ||
339                             (!id.compare ("selection") && *it == selection_range.get()) ||
340                             (!id.compare ((*it)->id().to_s()))) {
341                                 TimespanPtr timespan = handler->add_timespan();
342                                 timespan->set_name ((*it)->name());
343                                 timespan->set_range_id (id);
344                                 timespan->set_range ((*it)->start(), (*it)->end());
345                                 state->timespans->push_back (timespan);
346                         }
347                 }
348         }
349         
350         if ((prop = root.property ("format"))) {
351                 state->time_format = (TimeFormat) string_2_enum (prop->value(), TimeFormat);
352         }
353         
354         return state;
355 }
356
357 XMLNode &
358 ExportProfileManager::serialize_timespan (TimespanStatePtr state)
359 {
360         XMLNode & root = *(new XMLNode ("ExportTimespan"));
361         XMLNode * span;
362         
363         update_ranges ();
364         
365         for (TimespanList::iterator it = state->timespans->begin(); it != state->timespans->end(); ++it) {
366                 if ((span = root.add_child ("Range"))) {
367                         span->add_property ("id", (*it)->range_id());
368                 }
369         }
370         
371         root.add_property ("format", enum_2_string (state->time_format));
372         
373         return root;
374 }
375
376 void
377 ExportProfileManager::update_ranges () {
378         ranges->clear();
379
380         /* Session */
381
382         session_range->set_name (_("Session"));
383         session_range->set (session.current_start_frame(), session.current_end_frame());
384         ranges->push_back (session_range.get());
385         
386         /* Selection */
387         
388         if (selection_range) {
389                 ranges->push_back (selection_range.get());
390         }
391         
392         /* ranges */
393         
394         LocationList const & list (session.locations()->list());
395         for (LocationList::const_iterator it = list.begin(); it != list.end(); ++it) {
396                 if ((*it)->is_range_marker()) {
397                         ranges->push_back (*it);
398                 }
399         }
400 }
401
402 bool
403 ExportProfileManager::init_channel_configs (XMLNodeList nodes)
404 {
405         channel_configs.clear();
406
407         if (nodes.empty()) {    
408                 ChannelConfigStatePtr config (new ChannelConfigState (handler->add_channel_config()));
409                 channel_configs.push_back (config);
410                 return false;
411         }
412         
413         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
414                 ChannelConfigStatePtr config (new ChannelConfigState (handler->add_channel_config()));
415                 config->config->set_state (**it);
416                 channel_configs.push_back (config);
417         }
418         
419         return true;
420 }
421
422 ExportProfileManager::FormatStatePtr
423 ExportProfileManager::duplicate_format_state (FormatStatePtr state)
424 {
425         /* Note: The pointer in the new FormatState should point to the same format spec
426                  as the original state's pointer. The spec itself should not be copied!   */
427
428         FormatStatePtr format (new FormatState (format_list, state->format));
429         formats.push_back (format);
430         return format;
431 }
432
433 void
434 ExportProfileManager::remove_format_state (FormatStatePtr state)
435 {
436         for (FormatStateList::iterator it = formats.begin(); it != formats.end(); ++it) {
437                 if (*it == state) {
438                         formats.erase (it);
439                         return;
440                 }
441         }
442 }
443
444 sys::path
445 ExportProfileManager::save_format_to_disk (FormatPtr format)
446 {
447         // TODO filename character stripping
448
449         /* Get filename for file */
450
451         Glib::ustring new_name = format->name();
452         new_name += export_format_suffix;
453         
454         sys::path new_path (export_config_dir);
455         new_path /= new_name;
456
457         /* Check if format is on disk already */
458         FileMap::iterator it;
459         if ((it = format_file_map.find (format->id())) != format_file_map.end()) {
460                 /* Update data */
461                 {
462                         XMLTree tree (it->second.to_string());
463                         tree.set_root (&format->get_state());
464                         tree.write();
465                 }
466                 
467                 /* Rename if necessary */
468                 
469                 if (new_name.compare (it->second.leaf())) {
470                         sys::rename (it->second, new_path);
471                 }
472                 
473         } else {
474                 /* Write new file */
475                 
476                 XMLTree tree (new_path.to_string());
477                 tree.set_root (&format->get_state());
478                 tree.write();
479         }
480         
481         FormatListChanged ();
482         return new_path;
483 }
484
485 void
486 ExportProfileManager::remove_format_profile (FormatPtr format)
487 {
488         for (FormatList::iterator it = format_list->begin(); it != format_list->end(); ++it) {
489                 if (*it == format) {
490                         format_list->erase (it);
491                         break;
492                 }
493         }
494
495         FileMap::iterator it = format_file_map.find (format->id());
496         if (it != format_file_map.end()) {
497                 sys::remove (it->second);
498                 format_file_map.erase (it);
499         }
500         
501         FormatListChanged ();
502 }
503
504 ExportProfileManager::FormatPtr
505 ExportProfileManager::get_new_format (FormatPtr original)
506 {       
507         FormatPtr format;
508         if (original) {
509                 format.reset (new ExportFormatSpecification (*original));
510         } else {
511                 format = handler->add_format();
512                 format->set_name ("empty format");
513         }
514         
515         sys::path path = save_format_to_disk (format);
516         FilePair pair (format->id(), path);
517         format_file_map.insert (pair);
518         
519         format_list->push_back (format);
520         FormatListChanged ();
521         
522         return format;
523 }
524
525 bool
526 ExportProfileManager::init_formats (XMLNodeList nodes)
527 {
528         formats.clear();
529
530         bool ok = true;
531         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
532                 FormatStatePtr format = deserialize_format (**it);
533                 if (format) {
534                         formats.push_back (format);
535                 } else { ok = false; }
536         }
537         
538         if (formats.empty ()) {
539                 FormatStatePtr format (new FormatState (format_list, FormatPtr ()));
540                 formats.push_back (format);
541                 return false;
542         }
543         
544         return ok;
545 }
546
547 ExportProfileManager::FormatStatePtr
548 ExportProfileManager::deserialize_format (XMLNode & root)
549 {
550         XMLProperty * prop;
551         UUID id;
552         
553         if ((prop = root.property ("id"))) {
554                 id = prop->value();
555         }
556         
557         for (FormatList::iterator it = format_list->begin(); it != format_list->end(); ++it) {
558                 if ((*it)->id() == id) {
559                         return FormatStatePtr (new FormatState (format_list, *it));
560                 }
561         }
562
563         return FormatStatePtr ();
564 }
565
566 XMLNode &
567 ExportProfileManager::serialize_format (FormatStatePtr state)
568 {
569         XMLNode * root = new XMLNode ("ExportFormat");
570         
571         string id = state->format ? state->format->id().to_s() : "";
572         root->add_property ("id", id);
573         
574         return *root;
575 }
576
577 void
578 ExportProfileManager::load_formats ()
579 {
580         vector<sys::path> found = find_file (string_compose ("*%1", export_format_suffix));
581
582         for (vector<sys::path>::iterator it = found.begin(); it != found.end(); ++it) {
583                 load_format_from_disk (*it);
584         }
585 }
586
587 void
588 ExportProfileManager::load_format_from_disk (PBD::sys::path const & path)
589 {
590         XMLTree const tree (path.to_string());
591         FormatPtr format = handler->add_format (*tree.root());
592         format_list->push_back (format);
593         
594         /* Handle id to filename mapping */
595         
596         FilePair pair (format->id(), path);
597         format_file_map.insert (pair);
598         
599         FormatListChanged ();
600 }
601
602 ExportProfileManager::FilenameStatePtr
603 ExportProfileManager::duplicate_filename_state (FilenameStatePtr state)
604 {
605         FilenameStatePtr filename (new FilenameState (handler->add_filename_copy (state->filename)));
606         filenames.push_back (filename);
607         return filename;
608 }
609
610 void
611 ExportProfileManager::remove_filename_state (FilenameStatePtr state)
612 {
613         for (FilenameStateList::iterator it = filenames.begin(); it != filenames.end(); ++it) {
614                 if (*it == state) {
615                         filenames.erase (it);
616                         return;
617                 }
618         }
619 }
620
621 bool
622 ExportProfileManager::init_filenames (XMLNodeList nodes)
623 {
624         filenames.clear ();
625
626         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
627                 FilenamePtr filename = handler->add_filename();
628                 filename->set_state (**it);
629                 filenames.push_back (FilenameStatePtr (new FilenameState (filename)));
630         }
631         
632         if (filenames.empty()) {
633                 FilenameStatePtr filename (new FilenameState (handler->add_filename()));
634                 filenames.push_back (filename);
635                 return false;
636         }
637         
638         return true;
639 }
640
641 boost::shared_ptr<ExportProfileManager::Warnings>
642 ExportProfileManager::get_warnings ()
643 {
644         boost::shared_ptr<Warnings> warnings (new Warnings ());
645
646         ChannelConfigStatePtr channel_config_state = channel_configs.front();
647         TimespanStatePtr timespan_state = timespans.front();
648         
649         /*** Check "global" config ***/
650         
651         TimespanListPtr timespans = timespan_state->timespans;
652         ChannelConfigPtr channel_config = channel_config_state->config;
653
654         /* Check Timespans are not empty */
655         
656         if (timespans->empty()) {
657                 warnings->errors.push_back (_("No timespan has been selected!"));
658         }
659
660         /* Check channel config ports */
661         
662         if (!channel_config->all_channels_have_ports ()) {
663                 warnings->warnings.push_back (_("Some channels are empty"));
664         }
665         
666         /*** Check files ***/
667         
668         FormatStateList::const_iterator format_it;
669         FilenameStateList::const_iterator filename_it;
670         for (format_it = formats.begin(), filename_it = filenames.begin();
671              format_it != formats.end() && filename_it != filenames.end();
672              ++format_it, ++filename_it) {
673                         check_config (warnings, timespan_state, channel_config_state, *format_it, *filename_it);
674         }
675         
676         return warnings;
677 }
678
679 void
680 ExportProfileManager::check_config (boost::shared_ptr<Warnings> warnings,
681                                     TimespanStatePtr timespan_state,
682                                     ChannelConfigStatePtr channel_config_state,
683                                     FormatStatePtr format_state,
684                                     FilenameStatePtr filename_state)
685 {
686         TimespanListPtr timespans = timespan_state->timespans;
687         ChannelConfigPtr channel_config = channel_config_state->config;
688         FormatPtr format = format_state->format;
689         FilenamePtr filename = filename_state->filename;
690
691         /* Check format and maximum channel count */
692         if (!format || !format->type()) {
693                 warnings->errors.push_back (_("No format selected!"));
694         } else if (format->channel_limit() < channel_config->get_n_chans()) {
695                 warnings->errors.push_back
696                   (string_compose (_("%1 supports only %2 channels, but you have %3 channels in your channel configuration"),
697                                      format->format_name(),
698                                      format->channel_limit(),
699                                      channel_config->get_n_chans()));
700         }
701         
702         if (!warnings->errors.empty()) { return; }
703         
704         /* Check filenames */
705         
706 //      filename->include_timespan = (timespans->size() > 1); Disabled for now...
707         
708         for (std::list<TimespanPtr>::iterator timespan_it = timespans->begin(); timespan_it != timespans->end(); ++timespan_it) {
709                 filename->set_timespan (*timespan_it);
710         
711                 if (channel_config->get_split()) {
712                         filename->include_channel = true;
713                         
714                         for (uint32_t chan = 1; chan <= channel_config->get_n_chans(); ++chan) {
715                                 filename->set_channel (chan);
716                                 
717                                 Glib::ustring path = filename->get_path (format);
718                                 
719                                 if (sys::exists (sys::path (path))) {
720                                         warnings->conflicting_filenames.push_back (path);
721                                 }
722                         }
723                         
724                 } else {
725                         Glib::ustring path = filename->get_path (format);
726                         
727                         if (sys::exists (sys::path (path))) {
728                                 warnings->conflicting_filenames.push_back (path);
729                         }
730                 }
731         }
732 }
733
734 }; // namespace ARDOUR