1 /*
2  *  EasyTAG - Tag editor for audio files
3  *  Copyright (C) 2014  Santtu Lakkala <inz@inz.fi>
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include "config.h"
21 
22 #ifdef ENABLE_MP4
23 
24 #include "gio_wrapper.h"
25 
GIO_InputStream(GFile * file_)26 GIO_InputStream::GIO_InputStream (GFile * file_) :
27     file ((GFile *)g_object_ref (gpointer (file_))),
28     filename (g_file_get_uri (file)),
29     error (NULL)
30 {
31     stream = g_file_read (file, NULL, &error);
32 }
33 
~GIO_InputStream()34 GIO_InputStream::~GIO_InputStream ()
35 {
36     clear ();
37 
38     g_clear_object (&stream);
39     g_free (filename);
40     g_object_unref (file);
41 }
42 
43 TagLib::FileName
name() const44 GIO_InputStream::name () const
45 {
46     return TagLib::FileName (filename);
47 }
48 
49 TagLib::ByteVector
readBlock(TagLib::ulong len)50 GIO_InputStream::readBlock (TagLib::ulong len)
51 {
52     if (error)
53     {
54         return TagLib::ByteVector::null;
55     }
56 
57     TagLib::ByteVector rv (len, 0);
58     gsize bytes;
59     g_input_stream_read_all (G_INPUT_STREAM (stream), (void *)rv.data (),
60                              len, &bytes, NULL, &error);
61 
62     return rv.resize (bytes);
63 }
64 
65 void
writeBlock(TagLib::ByteVector const & data)66 GIO_InputStream::writeBlock (TagLib::ByteVector const &data)
67 {
68     g_warning ("%s", "Trying to write to read-only file!");
69 }
70 
71 void
insert(TagLib::ByteVector const & data,TagLib::ulong start,TagLib::ulong replace)72 GIO_InputStream::insert (TagLib::ByteVector const &data,
73                          TagLib::ulong start,
74                          TagLib::ulong replace)
75 {
76     g_warning ("%s", "Trying to write to read-only file!");
77 }
78 
79 void
removeBlock(TagLib::ulong start,TagLib::ulong len)80 GIO_InputStream::removeBlock (TagLib::ulong start, TagLib::ulong len)
81 {
82     g_warning ("%s", "Trying to write to read-only file!");
83 }
84 
85 bool
readOnly() const86 GIO_InputStream::readOnly () const
87 {
88     return true;
89 }
90 
91 bool
isOpen() const92 GIO_InputStream::isOpen () const
93 {
94     return !!stream;
95 }
96 
97 void
seek(long int offset,TagLib::IOStream::Position p)98 GIO_InputStream::seek (long int offset, TagLib::IOStream::Position p)
99 {
100     if (error)
101     {
102         return;
103     }
104 
105     GSeekType type;
106 
107     switch (p)
108     {
109         case TagLib::IOStream::Beginning:
110             type = G_SEEK_SET;
111             break;
112         case TagLib::IOStream::Current:
113             type = G_SEEK_CUR;
114             break;
115         case TagLib::IOStream::End:
116             type = G_SEEK_END;
117             break;
118         default:
119             g_warning ("Unknown seek");
120             return;
121     }
122 
123     g_seekable_seek (G_SEEKABLE (stream), offset, type, NULL, &error);
124 }
125 
126 void
clear()127 GIO_InputStream::clear ()
128 {
129     if (error)
130     {
131         g_error_free(error);
132         error = NULL;
133     }
134 }
135 
136 long int
tell() const137 GIO_InputStream::tell () const
138 {
139     return g_seekable_tell (G_SEEKABLE (stream));
140 }
141 
142 long int
length()143 GIO_InputStream::length ()
144 {
145     if (error)
146     {
147         return -1;
148     }
149 
150     long int rv = -1;
151     GFileInfo *info = g_file_input_stream_query_info (stream,
152                                                       G_FILE_ATTRIBUTE_STANDARD_SIZE,
153                                                       NULL, &error);
154     if (info)
155     {
156         rv = g_file_info_get_size (info);
157         g_object_unref (info);
158     }
159 
160     return rv;
161 }
162 
163 void
truncate(long int len)164 GIO_InputStream::truncate (long int len)
165 {
166     g_warning ("%s", "Trying to truncate read-only file");
167 }
168 
GIO_IOStream(GFile * file_)169 GIO_IOStream::GIO_IOStream (GFile *file_) :
170     file ((GFile *)g_object_ref (gpointer (file_))),
171     filename (g_file_get_uri (file_)),
172     error (NULL)
173 {
174     stream = g_file_open_readwrite (file, NULL, &error);
175 }
176 
177 const GError *
getError() const178 GIO_InputStream::getError () const
179 {
180     return error;
181 }
182 
~GIO_IOStream()183 GIO_IOStream::~GIO_IOStream ()
184 {
185     clear ();
186 
187     if (stream)
188     {
189         g_object_unref (stream);
190     }
191 
192     g_free (filename);
193     g_object_unref (G_OBJECT (file));
194 }
195 
196 TagLib::FileName
name() const197 GIO_IOStream::name () const
198 {
199     return TagLib::FileName (filename);
200 }
201 
202 TagLib::ByteVector
readBlock(TagLib::ulong len)203 GIO_IOStream::readBlock (TagLib::ulong len)
204 {
205     if (error)
206     {
207         return TagLib::ByteVector::null;
208     }
209 
210     gsize bytes = 0;
211     TagLib::ByteVector rv (len, 0);
212     GInputStream *istream = g_io_stream_get_input_stream (G_IO_STREAM (stream));
213     g_input_stream_read_all (istream,
214                              (void *)rv.data (), len,
215                              &bytes,
216                              NULL, &error);
217 
218     return rv.resize(bytes);
219 }
220 
221 void
writeBlock(TagLib::ByteVector const & data)222 GIO_IOStream::writeBlock (TagLib::ByteVector const &data)
223 {
224     if (error)
225     {
226         return;
227     }
228 
229     gsize bytes_written;
230     GOutputStream *ostream = g_io_stream_get_output_stream (G_IO_STREAM (stream));
231 
232     if (!g_output_stream_write_all (ostream, data.data (), data.size (),
233                                     &bytes_written, NULL, &error))
234     {
235         g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %u bytes of data were "
236                  "written", bytes_written, data.size ());
237     }
238 }
239 
240 void
insert(TagLib::ByteVector const & data,TagLib::ulong start,TagLib::ulong replace)241 GIO_IOStream::insert (TagLib::ByteVector const &data,
242                       TagLib::ulong start,
243                       TagLib::ulong replace)
244 {
245     if (error)
246     {
247         return;
248     }
249 
250     if (data.size () == replace)
251     {
252         seek (start, TagLib::IOStream::Beginning);
253         writeBlock (data);
254         return;
255     }
256     else if (data.size () < replace)
257     {
258         removeBlock (start, replace - data.size ());
259         seek (start);
260         writeBlock (data);
261         return;
262     }
263 
264     GFileIOStream *tstr;
265     GFile *tmp = g_file_new_tmp ("easytag-XXXXXX", &tstr, &error);
266 
267     if (tmp == NULL)
268     {
269         return;
270     }
271 
272     char buffer[4096];
273     gsize r;
274 
275     GOutputStream *ostream = g_io_stream_get_output_stream (G_IO_STREAM (tstr));
276     GInputStream *istream = g_io_stream_get_input_stream (G_IO_STREAM (stream));
277 
278     seek (0);
279 
280     while (g_input_stream_read_all (istream, buffer,
281                                     MIN (G_N_ELEMENTS (buffer), start),
282                                     &r, NULL, &error) && r > 0)
283     {
284         gsize w;
285         g_output_stream_write_all (ostream, buffer, r, &w, NULL, &error);
286 
287         if (w != r)
288         {
289             g_warning ("%s", "Unable to write all bytes");
290         }
291 
292 	if (error)
293 	{
294             g_object_unref (tstr);
295             g_object_unref (tmp);
296             return;
297 	}
298 
299         start -= r;
300     }
301 
302     if (error)
303     {
304         g_object_unref (tstr);
305         g_object_unref (tmp);
306         return;
307     }
308 
309     g_output_stream_write_all (ostream, data.data (), data.size (), NULL,
310                                NULL, &error);
311     seek (replace, TagLib::IOStream::Current);
312 
313     if (error)
314     {
315         g_object_unref (tstr);
316         g_object_unref (tmp);
317         return;
318     }
319 
320     while (g_input_stream_read_all (istream, buffer, sizeof (buffer), &r,
321                                     NULL, &error) && r > 0)
322     {
323         gsize bytes_written;
324 
325         if (!g_output_stream_write_all (ostream, buffer, r, &bytes_written,
326                                         NULL, &error))
327         {
328             g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %" G_GSIZE_FORMAT
329                      " bytes of data were written", bytes_written, r);
330             g_object_unref (tstr);
331             g_object_unref (tmp);
332             return;
333         }
334     }
335 
336     g_object_unref (tstr);
337     g_object_unref (stream);
338     stream = NULL;
339 
340     g_file_move (tmp, file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error);
341 
342     if (error)
343     {
344             g_object_unref (tmp);
345 	    return;
346     }
347 
348     stream = g_file_open_readwrite (file, NULL, &error);
349 
350     g_object_unref (tmp);
351 }
352 
353 void
removeBlock(TagLib::ulong start,TagLib::ulong len)354 GIO_IOStream::removeBlock (TagLib::ulong start, TagLib::ulong len)
355 {
356     if (start + len >= (TagLib::ulong)length ())
357     {
358         truncate (start);
359         return;
360     }
361 
362     char *buffer[4096];
363     gsize r;
364     GInputStream *istream = g_io_stream_get_input_stream (G_IO_STREAM (stream));
365     GOutputStream *ostream = g_io_stream_get_output_stream (G_IO_STREAM (stream));
366     seek (start + len);
367 
368     while (g_input_stream_read_all (istream, buffer, sizeof (buffer), &r, NULL,
369                                     NULL) && r > 0)
370     {
371         gsize bytes_written;
372 
373         seek (start);
374 
375         if (!g_output_stream_write_all (ostream, buffer, r, &bytes_written,
376                                         NULL, &error))
377         {
378             g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %" G_GSIZE_FORMAT
379                      " bytes of data were written", bytes_written, r);
380             return;
381         }
382 
383         start += r;
384         seek (start + len);
385     }
386 
387     truncate (start);
388 }
389 
390 bool
readOnly() const391 GIO_IOStream::readOnly () const
392 {
393     return !stream;
394 }
395 
396 bool
isOpen() const397 GIO_IOStream::isOpen () const
398 {
399     return !!stream;
400 }
401 
402 void
seek(long int offset,TagLib::IOStream::Position p)403 GIO_IOStream::seek (long int offset, TagLib::IOStream::Position p)
404 {
405     if (error)
406     {
407         return;
408     }
409 
410     GSeekType type;
411 
412     switch (p)
413     {
414         case TagLib::IOStream::Beginning:
415             type = G_SEEK_SET;
416             break;
417         case TagLib::IOStream::Current:
418             type = G_SEEK_CUR;
419             break;
420         case TagLib::IOStream::End:
421             type = G_SEEK_END;
422             break;
423         default:
424             g_warning ("%s", "Unknown seek");
425             return;
426     }
427 
428     g_seekable_seek (G_SEEKABLE (stream), offset, type, NULL, &error);
429 }
430 
431 void
clear()432 GIO_IOStream::clear ()
433 {
434     g_clear_error (&error);
435 }
436 
437 long int
tell() const438 GIO_IOStream::tell () const
439 {
440     return g_seekable_tell (G_SEEKABLE (stream));
441 }
442 
443 long int
length()444 GIO_IOStream::length ()
445 {
446     long rv = -1;
447 
448     if (error)
449     {
450 	return rv;
451     }
452 
453     GFileInfo *info = g_file_io_stream_query_info (stream,
454                                                    G_FILE_ATTRIBUTE_STANDARD_SIZE,
455                                                    NULL, &error);
456 
457     if (info)
458     {
459         rv = g_file_info_get_size (info);
460         g_object_unref (info);
461     }
462 
463     return rv;
464 }
465 
466 void
truncate(long int len)467 GIO_IOStream::truncate (long int len)
468 {
469     if (error)
470     {
471         return;
472     }
473 
474     g_seekable_truncate (G_SEEKABLE (stream), len, NULL, &error);
475 }
476 
getError() const477 const GError *GIO_IOStream::getError () const
478 {
479     return error;
480 }
481 
482 #endif /* ENABLE_MP4 */
483