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