1 /*
2 * libInstPatch
3 * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public License
7 * as published by the Free Software Foundation; version 2.1
8 * of the License only.
9 *
10 * This library 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 Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 * 02110-1301, USA or on the web at http://www.gnu.org.
19 */
20 /**
21 * SECTION: IpatchBase
22 * @short_description: Base instrument file object type
23 * @see_also:
24 * @stability: Stable
25 *
26 * Defines an abstract object type which is used as the basis of instrument
27 * files, such as #IpatchSF2, #IpatchDLS, etc.
28 */
29 #include <stdio.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <errno.h>
33 #include <glib.h>
34 #include <glib/gstdio.h>
35 #include <glib-object.h>
36 #include "IpatchBase.h"
37 #include "IpatchConverter.h"
38 #include "IpatchParamProp.h"
39 #include "IpatchTypeProp.h"
40 #include "util.h"
41 #include "ipatch_priv.h"
42
43 enum
44 {
45 PROP_0,
46 PROP_CHANGED,
47 PROP_SAVED,
48 PROP_FILENAME,
49 PROP_FILE
50 };
51
52
53 static void ipatch_base_finalize(GObject *gobject);
54 static void ipatch_base_set_property(GObject *object, guint property_id,
55 const GValue *value, GParamSpec *pspec);
56 static void ipatch_base_get_property(GObject *object, guint property_id,
57 GValue *value, GParamSpec *pspec);
58 static void ipatch_base_real_set_file(IpatchBase *base, IpatchFile *file);
59 static void ipatch_base_real_set_file_name(IpatchBase *base,
60 const char *file_name);
61 static gboolean ipatch_base_real_save(IpatchBase *base, const char *filename,
62 gboolean save_a_copy, GError **err);
63
64 /* private var used by IpatchItem, for fast "changed" property notifies */
65 GParamSpec *ipatch_base_pspec_changed;
66
67 /* cached parameter specs to speed up prop notifies */
68 static GParamSpec *file_pspec;
69 static GParamSpec *file_name_pspec;
70
G_DEFINE_ABSTRACT_TYPE(IpatchBase,ipatch_base,IPATCH_TYPE_CONTAINER)71 G_DEFINE_ABSTRACT_TYPE(IpatchBase, ipatch_base, IPATCH_TYPE_CONTAINER)
72
73
74 /**
75 * ipatch_base_type_get_mime_type:
76 * @base_type: #IpatchBase derived type to get mime type of
77 *
78 * Get the mime type of the file type associated with the given base patch file type.
79 *
80 * Returns: Mime type or NULL if none assigned for this base type.
81 * Free the string when finished with it.
82 *
83 * Since: 1.1.0
84 */
85 char *
86 ipatch_base_type_get_mime_type(GType base_type)
87 {
88 const IpatchConverterInfo *info;
89 char *mime_type;
90
91 info = ipatch_lookup_converter_info(0, base_type, IPATCH_TYPE_FILE);
92
93 if(!info)
94 {
95 return (NULL);
96 }
97
98 ipatch_type_get(info->dest_type, "mime-type", &mime_type, NULL);
99
100 return (mime_type);
101 }
102
103 static void
ipatch_base_class_init(IpatchBaseClass * klass)104 ipatch_base_class_init(IpatchBaseClass *klass)
105 {
106 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
107 IpatchItemClass *item_class = IPATCH_ITEM_CLASS(klass);
108
109 item_class->item_set_property = ipatch_base_set_property;
110 obj_class->get_property = ipatch_base_get_property;
111 obj_class->finalize = ipatch_base_finalize;
112
113 ipatch_base_pspec_changed =
114 g_param_spec_boolean("changed", "Changed", "Changed Flag",
115 TRUE, G_PARAM_READWRITE | IPATCH_PARAM_NO_SAVE_CHANGE
116 | IPATCH_PARAM_NO_SAVE);
117 g_object_class_install_property(obj_class, PROP_CHANGED,
118 ipatch_base_pspec_changed);
119
120 g_object_class_install_property(obj_class, PROP_SAVED,
121 g_param_spec_boolean("saved", "Saved", "Been Saved Flag",
122 FALSE, G_PARAM_READWRITE
123 | IPATCH_PARAM_NO_SAVE_CHANGE
124 | IPATCH_PARAM_NO_SAVE));
125 file_name_pspec = g_param_spec_string("file-name", "File Name",
126 "File Name", "untitled",
127 G_PARAM_READWRITE
128 | IPATCH_PARAM_NO_SAVE_CHANGE);
129 g_object_class_install_property(obj_class, PROP_FILENAME, file_name_pspec);
130
131 file_pspec = g_param_spec_object("file", "File", "File Object",
132 IPATCH_TYPE_FILE,
133 G_PARAM_READWRITE | IPATCH_PARAM_NO_SAVE
134 | IPATCH_PARAM_HIDE
135 | IPATCH_PARAM_NO_SAVE_CHANGE);
136 g_object_class_install_property(obj_class, PROP_FILE, file_pspec);
137 }
138
139 static void
ipatch_base_init(IpatchBase * base)140 ipatch_base_init(IpatchBase *base)
141 {
142 }
143
144 /* function called when a patch is being destroyed */
145 static void
ipatch_base_finalize(GObject * gobject)146 ipatch_base_finalize(GObject *gobject)
147 {
148 IpatchBase *base = IPATCH_BASE(gobject);
149
150 IPATCH_ITEM_WLOCK(base);
151
152 if(base->file)
153 {
154 ipatch_file_unref_from_object(base->file, gobject); // -- unref file from object
155 }
156
157 base->file = NULL;
158
159 IPATCH_ITEM_WUNLOCK(base);
160
161 if(G_OBJECT_CLASS(ipatch_base_parent_class)->finalize)
162 {
163 G_OBJECT_CLASS(ipatch_base_parent_class)->finalize(gobject);
164 }
165 }
166
167 static void
ipatch_base_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)168 ipatch_base_set_property(GObject *object, guint property_id,
169 const GValue *value, GParamSpec *pspec)
170 {
171 IpatchBase *base = IPATCH_BASE(object);
172
173 switch(property_id)
174 {
175 case PROP_CHANGED:
176 if(g_value_get_boolean(value))
177 {
178 ipatch_item_set_flags(IPATCH_ITEM(base), IPATCH_BASE_CHANGED);
179 }
180 else
181 {
182 ipatch_item_clear_flags(IPATCH_ITEM(base), IPATCH_BASE_CHANGED);
183 }
184
185 break;
186
187 case PROP_SAVED:
188 if(g_value_get_boolean(value))
189 {
190 ipatch_item_set_flags(IPATCH_ITEM(base), IPATCH_BASE_SAVED);
191 }
192 else
193 {
194 ipatch_item_clear_flags(IPATCH_ITEM(base), IPATCH_BASE_SAVED);
195 }
196
197 break;
198
199 case PROP_FILENAME:
200 ipatch_base_real_set_file_name(base, g_value_get_string(value));
201 break;
202
203 case PROP_FILE:
204 ipatch_base_real_set_file(base, g_value_get_object(value));
205 break;
206
207 default:
208 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
209 break;
210 }
211 }
212
213 static void
ipatch_base_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)214 ipatch_base_get_property(GObject *object, guint property_id,
215 GValue *value, GParamSpec *pspec)
216 {
217 IpatchBase *base;
218
219 g_return_if_fail(IPATCH_IS_BASE(object));
220 base = IPATCH_BASE(object);
221
222 switch(property_id)
223 {
224 case PROP_CHANGED:
225 g_value_set_boolean(value,
226 ipatch_item_get_flags(IPATCH_ITEM(base))
227 & IPATCH_BASE_CHANGED);
228 break;
229
230 case PROP_SAVED:
231 g_value_set_boolean(value,
232 ipatch_item_get_flags(IPATCH_ITEM(base))
233 & IPATCH_BASE_SAVED);
234 break;
235
236 case PROP_FILENAME:
237 g_value_take_string(value, ipatch_base_get_file_name(base));
238 break;
239
240 case PROP_FILE:
241 g_value_take_object(value, ipatch_base_get_file(base));
242 break;
243
244 default:
245 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
246 break;
247 }
248 }
249
250 /**
251 * ipatch_base_set_file:
252 * @base: Patch base object to set file object of
253 * @file: File object
254 *
255 * Sets the file object associated with a patch.
256 */
257 void
ipatch_base_set_file(IpatchBase * base,IpatchFile * file)258 ipatch_base_set_file(IpatchBase *base, IpatchFile *file)
259 {
260 GValue value = { 0 }, oldval = { 0 };
261
262 g_return_if_fail(IPATCH_IS_BASE(base));
263 g_return_if_fail(IPATCH_IS_FILE(file));
264
265 g_value_init(&value, IPATCH_TYPE_FILE);
266 g_value_set_object(&value, file);
267
268 ipatch_item_get_property_fast((IpatchItem *)base, file_pspec, &oldval);
269 ipatch_base_real_set_file(base, file);
270 ipatch_item_prop_notify((IpatchItem *)base, file_pspec, &value, &oldval);
271
272 g_value_unset(&value);
273 g_value_unset(&oldval);
274 }
275
276 static void
ipatch_base_real_set_file(IpatchBase * base,IpatchFile * file)277 ipatch_base_real_set_file(IpatchBase *base, IpatchFile *file)
278 {
279 GValue value = { 0 }, oldval = { 0 };
280 IpatchFile *oldfile;
281
282 ipatch_file_ref_from_object(file, (GObject *)base); // ++ ref new file from object
283
284 IPATCH_ITEM_WLOCK(base);
285 oldfile = base->file;
286 base->file = file;
287 IPATCH_ITEM_WUNLOCK(base);
288
289 g_value_init(&oldval, G_TYPE_STRING);
290
291 if(oldfile)
292 {
293 g_value_take_string(&oldval, ipatch_file_get_name(oldfile));
294 ipatch_file_unref_from_object(oldfile, (GObject *)base); // -- remove reference to old file
295 }
296
297 g_value_init(&value, G_TYPE_STRING);
298 g_value_take_string(&value, ipatch_file_get_name(file));
299
300 // Notify file-name property change as well
301 ipatch_item_prop_notify((IpatchItem *)base, file_name_pspec, &value, &oldval);
302
303 g_value_unset(&value);
304 g_value_unset(&oldval);
305 }
306
307 /**
308 * ipatch_base_get_file:
309 * @base: Patch base object to get file object from
310 *
311 * Get the file object associated with a patch base object. Caller owns a
312 * reference to the returned file object and it should be unrefed when
313 * done with it.
314 *
315 * Returns: (transfer full): File object or %NULL if not set.
316 * Remember to unref it when done with it.
317 */
318 IpatchFile *
ipatch_base_get_file(IpatchBase * base)319 ipatch_base_get_file(IpatchBase *base)
320 {
321 IpatchFile *file;
322
323 g_return_val_if_fail(IPATCH_IS_BASE(base), NULL);
324
325 IPATCH_ITEM_RLOCK(base);
326 file = base->file;
327
328 if(file)
329 {
330 g_object_ref(file);
331 }
332
333 IPATCH_ITEM_RUNLOCK(base);
334
335 return (file);
336 }
337
338 /**
339 * ipatch_base_set_file_name:
340 * @base: Patch base object to set file name of
341 * @file_name: Path and name to set filename to
342 *
343 * Sets the file name of the file object in a patch base object. File object
344 * should have been set before calling this function (otherwise request is
345 * silently ignored). A convenience function as one could get the file object
346 * and set it directly.
347 */
348 void
ipatch_base_set_file_name(IpatchBase * base,const char * file_name)349 ipatch_base_set_file_name(IpatchBase *base, const char *file_name)
350 {
351 GValue value = { 0 }, oldval = { 0 };
352
353 g_return_if_fail(IPATCH_IS_BASE(base));
354
355 g_value_init(&value, G_TYPE_STRING);
356 g_value_set_string(&value, file_name);
357
358 ipatch_item_get_property_fast((IpatchItem *)base, file_name_pspec, &oldval);
359 ipatch_base_real_set_file_name(base, file_name);
360 ipatch_item_prop_notify((IpatchItem *)base, file_name_pspec, &value, &oldval);
361
362 g_value_unset(&value);
363 g_value_unset(&oldval);
364 }
365
366 /* the real set file name routine, user routine does a notify */
367 static void
ipatch_base_real_set_file_name(IpatchBase * base,const char * file_name)368 ipatch_base_real_set_file_name(IpatchBase *base, const char *file_name)
369 {
370 IPATCH_ITEM_RLOCK(base);
371
372 if(!base->file) /* silently fail */
373 {
374 IPATCH_ITEM_RUNLOCK(base);
375 return;
376 }
377
378 ipatch_file_set_name(base->file, file_name);
379 IPATCH_ITEM_RUNLOCK(base);
380 }
381
382 /**
383 * ipatch_base_get_file_name:
384 * @base: Base patch item to get file name from.
385 *
386 * Get the file name of the file object in a base patch item. A convenience
387 * function.
388 *
389 * Returns: New allocated file name or %NULL if not set or file object
390 * not set. String should be freed when finished with it.
391 */
392 char *
ipatch_base_get_file_name(IpatchBase * base)393 ipatch_base_get_file_name(IpatchBase *base)
394 {
395 char *file_name = NULL;
396
397 g_return_val_if_fail(IPATCH_IS_BASE(base), NULL);
398
399 IPATCH_ITEM_RLOCK(base);
400
401 if(base->file)
402 {
403 file_name = ipatch_file_get_name(base->file);
404 }
405
406 IPATCH_ITEM_RUNLOCK(base);
407
408 return (file_name);
409 }
410
411 /**
412 * ipatch_base_find_unused_midi_locale:
413 * @base: Patch base object
414 * @bank: (inout): MIDI bank number
415 * @program: (inout): MIDI program number
416 * @exclude: (nullable): Child of @base with MIDI locale to exclude or %NULL
417 * @percussion: Set to %TRUE to find a free percussion MIDI locale
418 *
419 * Finds an unused MIDI locale (bank:program number pair) in a patch
420 * base object. The way in which MIDI bank and program numbers are
421 * used is implementation dependent. Percussion instruments often
422 * affect the bank parameter (for example SoundFont uses bank 128 for
423 * percussion presets). On input the @bank and @program parameters
424 * set the initial locale to start from (set to 0:0 to find the first
425 * free value). If the @percussion parameter is set it may affect
426 * @bank, if its not set, bank will not be modified (e.g., if bank is
427 * a percussion value it will be used). The exclude parameter can be
428 * set to a child item of @base to exclude from the list of "used"
429 * locales (useful when making an item unique that is already parented
430 * to @base). On return @bank and @program will be set to an unused
431 * MIDI locale based on the input criteria.
432 */
433 void
ipatch_base_find_unused_midi_locale(IpatchBase * base,int * bank,int * program,const IpatchItem * exclude,gboolean percussion)434 ipatch_base_find_unused_midi_locale(IpatchBase *base, int *bank,
435 int *program, const IpatchItem *exclude,
436 gboolean percussion)
437 {
438 IpatchBaseClass *klass;
439
440 g_return_if_fail(IPATCH_IS_BASE(base));
441 g_return_if_fail(bank != NULL);
442 g_return_if_fail(program != NULL);
443
444 *bank = 0;
445 *program = 0;
446
447 klass = IPATCH_BASE_GET_CLASS(base);
448
449 if(klass && klass->find_unused_locale)
450 {
451 klass->find_unused_locale(base, bank, program, exclude, percussion);
452 }
453 }
454
455 /**
456 * ipatch_base_find_item_by_midi_locale:
457 * @base: Patch base object
458 * @bank: MIDI bank number of item to search for
459 * @program: MIDI program number of item to search for
460 *
461 * Find a child object in a base patch object which matches the given MIDI
462 * locale (@bank and @program numbers).
463 *
464 * Returns: (transfer full): The item or %NULL if not found. The caller owns a reference to the
465 * returned object, and is responsible for unref'ing when finished.
466 */
467 IpatchItem *
ipatch_base_find_item_by_midi_locale(IpatchBase * base,int bank,int program)468 ipatch_base_find_item_by_midi_locale(IpatchBase *base, int bank, int program)
469 {
470 IpatchBaseClass *klass;
471
472 g_return_val_if_fail(IPATCH_IS_BASE(base), NULL);
473
474 klass = IPATCH_BASE_GET_CLASS(base);
475
476 if(klass && klass->find_item_by_locale)
477 {
478 return (klass->find_item_by_locale(base, bank, program));
479 }
480 else
481 {
482 return (NULL);
483 }
484 }
485
486 /* GFunc used by g_list_foreach() to remove created sample stores */
487 static void
remove_created_stores(gpointer data,gpointer user_data)488 remove_created_stores(gpointer data, gpointer user_data)
489 {
490 IpatchSampleStore *store = data;
491 IpatchSampleData *sampledata;
492
493 sampledata = (IpatchSampleData *)ipatch_item_get_parent((IpatchItem *)store); // ++ ref parent sampledata
494
495 if(sampledata)
496 {
497 ipatch_sample_data_remove(sampledata, store);
498 }
499
500 g_object_unref(sampledata); // -- unref sampledata
501 }
502
503 /**
504 * ipatch_base_save:
505 * @base: Base item to save
506 * @err: Location to store error info or %NULL
507 *
508 * Save a patch item to the assigned filename or file object. This function handles
509 * saving over an existing file and migrates sample stores as needed.
510 *
511 * Returns: %TRUE on success, %FALSE otherwise (in which case @err may be set)
512 *
513 * Since: 1.1.0
514 */
515 gboolean
ipatch_base_save(IpatchBase * base,GError ** err)516 ipatch_base_save(IpatchBase *base, GError **err)
517 {
518 return ipatch_base_real_save(base, NULL, FALSE, err);
519 }
520
521 /**
522 * ipatch_base_save_to_filename:
523 * @base: Base item to save
524 * @filename: (nullable): New file name to save to or %NULL to use current one
525 * @err: Location to store error info or %NULL
526 *
527 * Save a patch item to a file. This function handles saving over an existing
528 * file and migrates sample stores as needed. It is an error to try to save
529 * over an open file that is not owned by @base.
530 *
531 * Returns: %TRUE on success, %FALSE otherwise (in which case @err may be set)
532 *
533 * Since: 1.1.0
534 */
535 gboolean
ipatch_base_save_to_filename(IpatchBase * base,const char * filename,GError ** err)536 ipatch_base_save_to_filename(IpatchBase *base, const char *filename, GError **err)
537 {
538 return ipatch_base_real_save(base, filename, FALSE, err);
539 }
540
541 /**
542 * ipatch_base_save_a_copy:
543 * @base: Base item to save
544 * @filename: File name to save a copy to
545 * @err: Location to store error info or %NULL
546 *
547 * Save a patch item to a file name.
548 *
549 * Returns: %TRUE on success, %FALSE otherwise (in which case @err may be set)
550 *
551 * Since: 1.1.0
552 */
553 gboolean
ipatch_base_save_a_copy(IpatchBase * base,const char * filename,GError ** err)554 ipatch_base_save_a_copy(IpatchBase *base, const char *filename, GError **err)
555 {
556 return ipatch_base_real_save(base, filename, TRUE, err);
557 }
558
559 /*
560 * ipatch_base_real_save:
561 * @base: Base item to save
562 * @filename: New file name to save to or %NULL to use current one
563 * @save_a_copy: If TRUE then new file object will not be assigned
564 * @err: Location to store error info or %NULL
565 *
566 * Save a patch item to a file. This function handles saving over an existing
567 * file and migrates sample stores as needed. It is an error to try to save
568 * over an open file that is not owned by @base though.
569 *
570 * Returns: %TRUE on success, %FALSE otherwise (in which case @err may be set)
571 */
572 static gboolean
ipatch_base_real_save(IpatchBase * base,const char * filename,gboolean save_a_copy,GError ** err)573 ipatch_base_real_save(IpatchBase *base, const char *filename, gboolean save_a_copy, GError **err)
574 {
575 const IpatchConverterInfo *info;
576 IpatchFile *lookup_file, *newfile = NULL, *oldfile = NULL;
577 char *tmp_fname = NULL, *abs_fname = NULL, *base_fname = NULL;
578 IpatchConverter *converter;
579 gboolean tempsave = FALSE; // Set to TRUE if writing to a temp file first, before replacing a file
580 GError *local_err = NULL;
581 IpatchList *created_stores = NULL;
582 int tmpfd;
583
584 g_return_val_if_fail(IPATCH_IS_BASE(base), FALSE);
585 g_return_val_if_fail(!err || !*err, FALSE);
586
587 g_object_get(base, "file", &oldfile, NULL); // ++ ref old file (if any)
588
589 /* Check if file name specified would overwrite another open file */
590 if(filename)
591 {
592 abs_fname = ipatch_util_abs_filename(filename); // ++ allocate absolute filename
593 lookup_file = ipatch_file_pool_lookup(abs_fname); // ++ ref file matching filename
594
595 if(lookup_file)
596 {
597 g_object_unref(lookup_file); // -- unref file (we only need the pointer value)
598 }
599
600 if(lookup_file && lookup_file != oldfile)
601 {
602 g_set_error(err, IPATCH_ERROR, IPATCH_ERROR_BUSY,
603 _("Refusing to save over other open file '%s'"), abs_fname);
604 goto error;
605 }
606 }
607
608 if(oldfile)
609 {
610 g_object_get(base, "file-name", &base_fname, NULL); // ++ allocate base file name
611 }
612
613 // Write to temporary file if saving over or new file name exists
614 tempsave = !abs_fname || (base_fname && strcmp(abs_fname, base_fname) == 0)
615 || g_file_test(abs_fname, G_FILE_TEST_EXISTS);
616
617 /* if no filename specified try to use current one */
618 if(!abs_fname)
619 {
620 if(!base_fname)
621 {
622 g_set_error(err, IPATCH_ERROR, IPATCH_ERROR_INVALID,
623 _("File name not supplied and none assigned"));
624 goto error;
625 }
626
627 abs_fname = base_fname; // !! abs_fname takes over base_fname
628 base_fname = NULL;
629 }
630 else
631 {
632 g_free(base_fname); // -- free base file name
633 }
634
635 /* Find a converter from base object to file */
636 info = ipatch_lookup_converter_info(0, G_OBJECT_TYPE(base), IPATCH_TYPE_FILE);
637
638 if(!info)
639 {
640 g_set_error(err, IPATCH_ERROR, IPATCH_ERROR_UNSUPPORTED,
641 _("Saving object of type '%s' to file '%s' not supported"),
642 g_type_name(G_OBJECT_TYPE(base)), abs_fname);
643 goto error;
644 }
645
646 if(tempsave) // Saving to a temporary file?
647 {
648 tmp_fname = g_strconcat(abs_fname, "_tmpXXXXXX", NULL); // ++ alloc temporary file name
649
650 // open temporary file in same directory as destination
651 if((tmpfd = g_mkstemp(tmp_fname)) == -1)
652 {
653 g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errno),
654 _("Unable to open temp file '%s' for writing: %s"),
655 tmp_fname, g_strerror(errno));
656 goto error;
657 }
658
659 newfile = IPATCH_FILE(g_object_new(info->dest_type, "file-name", tmp_fname, NULL)); /* ++ ref new file */
660 ipatch_file_assign_fd(newfile, tmpfd, TRUE); /* Assign file descriptor and set close on finalize */
661 }
662 else // Not replacing a file, just save it directly without using a temporary file
663 {
664 newfile = IPATCH_FILE(g_object_new(info->dest_type, "file-name", abs_fname, NULL)); /* ++ ref new file */
665 }
666
667 // ++ Create new converter and set create-stores property if not "save a copy" mode
668 converter = IPATCH_CONVERTER(g_object_new(info->conv_type, "create-stores", !save_a_copy, NULL));
669
670 ipatch_converter_add_input(converter, G_OBJECT(base));
671 ipatch_converter_add_output(converter, G_OBJECT(newfile));
672
673 /* attempt to save patch file */
674 if(!ipatch_converter_convert(converter, err))
675 {
676 g_object_unref(converter); // -- unref converter
677 goto error;
678 }
679
680 // If "create-stores" was set, then we get the list of stores in case of error, so new stores can be removed
681 if(!save_a_copy)
682 {
683 IpatchList *out_list = ipatch_converter_get_outputs(converter); // ++ ref output object list
684 created_stores = (IpatchList *)g_list_nth_data(out_list->items, 1);
685
686 if(created_stores)
687 {
688 g_object_ref(created_stores); // ++ ref created stores list
689 }
690
691 g_object_unref(out_list); // -- unref output object list
692 }
693
694 g_object_unref(converter); // -- unref converter
695
696 if(tempsave)
697 {
698 ipatch_file_assign_fd(newfile, -1, FALSE); // Unset file descriptor of file (now using file name), closes file descriptor
699 }
700
701 // Migrate samples
702 if(!save_a_copy)
703 {
704 if(!ipatch_migrate_file_sample_data(oldfile, newfile, abs_fname, IPATCH_SAMPLE_DATA_MIGRATE_REMOVE_NEW_IF_UNUSED
705 | IPATCH_SAMPLE_DATA_MIGRATE_TO_NEWFILE | (tempsave ? IPATCH_SAMPLE_DATA_MIGRATE_REPLACE : 0), err))
706 {
707 goto error;
708 }
709
710 ipatch_base_set_file(IPATCH_BASE(base), newfile); // Assign new file to base object
711 }
712 else if(tempsave && !ipatch_file_rename(newfile, abs_fname, err)) // If "save a copy" mode and saved to a temporary file, rename it here
713 {
714 goto error;
715 }
716
717 if(created_stores)
718 {
719 g_object_unref(created_stores); // -- unref created stores
720 }
721
722 g_object_unref(newfile); // -- unref creators reference
723 g_free(tmp_fname); // -- free temp file name
724 g_free(abs_fname); // -- free file name
725
726 if(oldfile)
727 {
728 g_object_unref(oldfile); // -- unref old file
729 }
730
731 return (TRUE);
732
733 error:
734
735 if(created_stores) // Remove new created stores
736 {
737 g_list_foreach(created_stores->items, remove_created_stores, NULL);
738 g_object_unref(created_stores); // -- unref created stores
739 }
740
741 if(newfile)
742 {
743 if(!ipatch_file_unlink(newfile, &local_err)) // -- Delete new file
744 {
745 g_warning(_("Failed to remove file after save failure: %s"),
746 ipatch_gerror_message(local_err));
747 g_clear_error(&local_err);
748 }
749
750 g_object_unref(newfile); // -- unref creators reference
751 }
752
753 g_free(tmp_fname); // -- free temp file name
754 g_free(abs_fname); // -- free file name
755
756 if(oldfile)
757 {
758 g_object_unref(oldfile); // -- unref old file
759 }
760
761 return (FALSE);
762 }
763
764 /**
765 * ipatch_base_close:
766 * @base: Base item to close
767 * @err: Location to store error info or %NULL
768 *
769 * Close a base instrument object (using ipatch_item_remove()), migrating sample data as needed.
770 *
771 * Returns: TRUE on success, FALSE otherwise (in which case @err may be set)
772 *
773 * Since: 1.1.0
774 */
775 gboolean
ipatch_base_close(IpatchBase * base,GError ** err)776 ipatch_base_close(IpatchBase *base, GError **err)
777 {
778 IpatchFile *file;
779
780 g_return_val_if_fail(IPATCH_IS_BASE(base), FALSE);
781 g_return_val_if_fail(!err || !*err, FALSE);
782
783 g_object_get(base, "file", &file, NULL); // ++ ref file (if any)
784
785 ipatch_item_remove(IPATCH_ITEM(base));
786
787 if(file && !ipatch_migrate_file_sample_data(file, NULL, NULL, 0, err))
788 {
789 g_object_unref(file); // -- unref file
790 return (FALSE);
791 }
792
793 g_object_unref(file); // -- unref file
794
795 return (TRUE);
796 }
797
798 /**
799 * ipatch_close_base_list:
800 * @list: List of base objects to close (non base objects are skipped)
801 * @err: Location to store error info or %NULL
802 *
803 * Close a list of base instrument objects (using ipatch_item_remove_recursive()),
804 * migrating sample data as needed. Using this function instead of ipatch_base_close()
805 * can save on unnecessary sample data migrations, if multiple base objects reference
806 * the same sample data.
807 *
808 * Returns: TRUE on success, FALSE otherwise (in which case @err may be set)
809 *
810 * Since: 1.1.0
811 */
812 gboolean
ipatch_close_base_list(IpatchList * list,GError ** err)813 ipatch_close_base_list(IpatchList *list, GError **err)
814 {
815 GList *p, *file_list = NULL;
816 IpatchFile *file;
817 gboolean retval = TRUE;
818 GError *local_err = NULL;
819 char *filename;
820
821 g_return_val_if_fail(IPATCH_IS_LIST(list), FALSE);
822 g_return_val_if_fail(!err || !*err, FALSE);
823
824 for(p = list->items; p; p = p->next)
825 {
826 if(!IPATCH_IS_BASE(p->data))
827 {
828 continue; // Just skip if its not a base object
829 }
830
831 g_object_get(p->data, "file", &file, NULL); // ++ ref file (if any)
832 ipatch_item_remove_recursive(IPATCH_ITEM(p->data), TRUE); // Recursively remove to release IpatchSampleData resources
833
834 if(file)
835 {
836 file_list = g_list_prepend(file_list, file);
837 }
838 }
839
840 file_list = g_list_reverse(file_list); // Reverse list to migrate samples in same order
841
842 for(p = file_list; p; p = g_list_delete_link(p, p))
843 {
844 file = p->data;
845
846 if(!ipatch_migrate_file_sample_data(file, NULL, NULL, 0, &local_err))
847 {
848 if(!retval || !err)
849 {
850 // Log additional errors
851 g_object_get(file, "file-name", &filename, NULL); // ++ alloc filename
852 g_critical(_("Error migrating samples from closed file '%s': %s"), filename,
853 ipatch_gerror_message(local_err));
854 g_free(filename); // -- free filename
855 g_clear_error(&local_err);
856 }
857 else
858 {
859 g_propagate_error(err, local_err); // Propagate first error
860 }
861
862 retval = FALSE;
863 }
864
865 g_object_unref(file); // -- unref file
866 }
867
868 return (retval);
869 }
870
871