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