sparsefile: keep it private as helper API for downloadbuffer
[platform/upstream/gstreamer.git] / plugins / elements / gstsparsefile.c
1 /* GStreamer
2  * Copyright (C) 2014 Wim Taymans <wtaymans@redhat.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <gst/gst.h>
25 #include <glib/gstdio.h>
26 #include <gio/gio.h>
27
28 #include "gstsparsefile.h"
29
30 #ifdef G_OS_WIN32
31 #include <io.h>                 /* lseek, open, close, read */
32 #undef lseek
33 #define lseek _lseeki64
34 #undef off_t
35 #define off_t guint64
36 #else
37 #include <unistd.h>
38 #endif
39
40 #ifdef HAVE_FSEEKO
41 #define FSEEK_FILE(file,offset)  (fseeko (file, (off_t) offset, SEEK_SET) != 0)
42 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
43 #define FSEEK_FILE(file,offset)  (lseek (fileno (file), (off_t) offset, SEEK_SET) == (off_t) -1)
44 #else
45 #define FSEEK_FILE(file,offset)  (fseek (file, offset, SEEK_SET) != 0)
46 #endif
47
48
49 typedef struct _GstSparseRange GstSparseRange;
50
51 struct _GstSparseRange
52 {
53   GstSparseRange *next;
54
55   gsize start;
56   gsize stop;
57 };
58
59 #define RANGE_CONTAINS(r,o) ((r)->start <= (o) && (r)->stop > (o))
60
61 struct _GstSparseFile
62 {
63   gint fd;
64   FILE *file;
65   gsize current_pos;
66
67   GstSparseRange *ranges;
68   guint n_ranges;
69
70   GstSparseRange *write_range;
71   GstSparseRange *read_range;
72 };
73
74 static GstSparseRange *
75 get_write_range (GstSparseFile * file, gsize offset)
76 {
77   GstSparseRange *next, *prev, *result = NULL;
78
79   if (file->write_range && file->write_range->stop == offset)
80     return file->write_range;
81
82   prev = NULL;
83   next = file->ranges;
84   while (next) {
85     if (next->start > offset)
86       break;
87
88     if (next->stop >= offset) {
89       result = next;
90       break;
91     }
92     prev = next;
93     next = next->next;
94   }
95   if (result == NULL) {
96     result = g_slice_new0 (GstSparseRange);
97     result->start = offset;
98     result->stop = offset;
99
100     result->next = next;
101     if (prev)
102       prev->next = result;
103     else
104       file->ranges = result;
105
106     file->write_range = result;
107     file->read_range = NULL;
108
109     file->n_ranges++;
110   }
111   return result;
112 }
113
114 static GstSparseRange *
115 get_read_range (GstSparseFile * file, gsize offset, gsize count)
116 {
117   GstSparseRange *walk, *result = NULL;
118
119   if (file->read_range && RANGE_CONTAINS (file->read_range, offset))
120     return file->read_range;
121
122   for (walk = file->ranges; walk; walk = walk->next) {
123     if (walk->start > offset)
124       break;
125
126     if (walk->stop >= offset + count) {
127       result = walk;
128       break;
129     }
130   }
131   return result;
132 }
133
134 /**
135  * gst_sparse_file_new:
136  *
137  * Make a new #GstSparseFile backed by the file represented with @fd.
138  *
139  * Returns: a new #GstSparseFile, gst_sparse_file_free() after usage.
140  *
141  * Since: 1.4
142  */
143 GstSparseFile *
144 gst_sparse_file_new (void)
145 {
146   GstSparseFile *result;
147
148   result = g_slice_new0 (GstSparseFile);
149   result->current_pos = 0;
150   result->ranges = NULL;
151   result->n_ranges = 0;
152
153   return result;
154 }
155
156 /**
157  * gst_sparse_file_set_fd:
158  * @file: a #GstSparseFile
159  * @fd: a file descriptor
160  *
161  * Store the data for @file in the file represented with @fd.
162  *
163  * Returns: %TRUE when @fd could be set
164  *
165  * Since: 1.4
166  */
167 gboolean
168 gst_sparse_file_set_fd (GstSparseFile * file, gint fd)
169 {
170   g_return_val_if_fail (file != NULL, FALSE);
171   g_return_val_if_fail (fd != 0, FALSE);
172
173   file->file = fdopen (fd, "wb+");
174   file->fd = fd;
175
176   return file->file != NULL;
177 }
178
179 /**
180  * gst_sparse_file_clear:
181  * @file: a #GstSparseFile
182  *
183  * Clear all the ranges in @file.
184  */
185 void
186 gst_sparse_file_clear (GstSparseFile * file)
187 {
188   g_return_if_fail (file != NULL);
189
190   if (file->file) {
191     fclose (file->file);
192     file->file = fdopen (file->fd, "wb+");
193   }
194   g_slice_free_chain (GstSparseRange, file->ranges, next);
195   file->current_pos = 0;
196   file->ranges = NULL;
197   file->n_ranges = 0;
198 }
199
200 /**
201  * gst_sparse_file_free:
202  * @file: a #GstSparseFile
203  *
204  * Free the memory used by @file.
205  *
206  * Since: 1.4
207  */
208 void
209 gst_sparse_file_free (GstSparseFile * file)
210 {
211   g_return_if_fail (file != NULL);
212
213   if (file->file) {
214     fflush (file->file);
215     fclose (file->file);
216   }
217   g_slice_free_chain (GstSparseRange, file->ranges, next);
218   g_slice_free (GstSparseFile, file);
219 }
220
221 /**
222  * gst_sparse_file_write:
223  * @file: a #GstSparseFile
224  * @offset: the offset
225  * @data: the data
226  * @count: amount of bytes
227  * @available: amount of bytes already available
228  * @error: a #GError
229  *
230  * Write @count bytes from @data to @file at @offset.
231  *
232  * If @available is not %NULL, it will be updated with the amount of
233  * data already available after the last written byte.
234  *
235  * Returns: The number of bytes written of 0 on error.
236  *
237  * Since: 1.4
238  */
239 gsize
240 gst_sparse_file_write (GstSparseFile * file, gsize offset, gconstpointer data,
241     gsize count, gsize * available, GError ** error)
242 {
243   GstSparseRange *range, *next;
244   gsize stop;
245
246   g_return_val_if_fail (file != NULL, 0);
247   g_return_val_if_fail (count != 0, 0);
248
249   if (file->file) {
250     if (file->current_pos != offset) {
251       GST_DEBUG ("seeking to %" G_GSIZE_FORMAT, offset);
252       if (FSEEK_FILE (file->file, offset))
253         goto error;
254     }
255     if (fwrite (data, count, 1, file->file) != 1)
256       goto error;
257   }
258
259   file->current_pos = offset + count;
260
261   /* update the new stop position in the range */
262   range = get_write_range (file, offset);
263   stop = offset + count;
264   range->stop = MAX (range->stop, stop);
265
266   /* see if we can merge with next region */
267   while ((next = range->next)) {
268     if (next->start > range->stop)
269       break;
270
271     GST_DEBUG ("merging range %" G_GSIZE_FORMAT "-%" G_GSIZE_FORMAT ", next %"
272         G_GSIZE_FORMAT "-%" G_GSIZE_FORMAT, range->start, range->stop,
273         next->start, next->stop);
274
275     range->stop = MAX (next->stop, range->stop);
276     range->next = next->next;
277
278     if (file->write_range == next)
279       file->write_range = NULL;
280     if (file->read_range == next)
281       file->read_range = NULL;
282     g_slice_free (GstSparseRange, next);
283     file->n_ranges--;
284   }
285   if (available)
286     *available = range->stop - stop;
287
288   return count;
289
290   /* ERRORS */
291 error:
292   {
293     g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
294         "Error writing file: %s", g_strerror (errno));
295     return 0;
296   }
297 }
298
299 /**
300  * gst_sparse_file_read:
301  * @file: a #GstSparseFile
302  * @offset: the offset
303  * @data: the data
304  * @count: amount of bytes
305  * @remaining: amount of bytes remaining
306  * @error: a #GError
307  *
308  * Read @count bytes from @file at @offset into @data.
309  *
310  * On error, @error will be set. If there are no @count bytes available
311  * at @offset, %G_IO_ERROR_WOULD_BLOCK is returned.
312  *
313  * @remaining will be set to the amount of bytes remaining in the read
314  * range.
315  *
316  * Returns: The number of bytes read of 0 on error.
317  *
318  * Since: 1.4
319  */
320 gsize
321 gst_sparse_file_read (GstSparseFile * file, gsize offset, gpointer data,
322     gsize count, gsize * remaining, GError ** error)
323 {
324   GstSparseRange *range;
325   gsize res = 0;
326
327   g_return_val_if_fail (file != NULL, 0);
328   g_return_val_if_fail (count != 0, 0);
329
330   if ((range = get_read_range (file, offset, count)) == NULL)
331     goto no_range;
332
333   if (file->file) {
334     if (file->current_pos != offset) {
335       GST_DEBUG ("seeking from %" G_GSIZE_FORMAT " to %" G_GSIZE_FORMAT,
336           file->current_pos, offset);
337       if (FSEEK_FILE (file->file, offset))
338         goto error;
339     }
340     res = fread (data, 1, count, file->file);
341   }
342
343   file->current_pos = offset + res;
344
345   if (G_UNLIKELY (res < count))
346     goto error;
347
348   if (remaining)
349     *remaining = range->stop - file->current_pos;
350
351   return count;
352
353   /* ERRORS */
354 no_range:
355   {
356     g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
357         "Offset not written to file yet");
358     return 0;
359   }
360 error:
361   {
362     if (ferror (file->file)) {
363       g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
364           "Error reading file: %s", g_strerror (errno));
365     } else if (feof (file->file)) {
366       return res;
367     }
368     return 0;
369   }
370 }
371
372 /**
373  * gst_sparse_file_n_ranges:
374  * @file: a #GstSparseFile
375  *
376  * Get the number of ranges that are written in @file.
377  *
378  * Returns: the number of written ranges.
379  *
380  * Since: 1.4
381  */
382 guint
383 gst_sparse_file_n_ranges (GstSparseFile * file)
384 {
385   g_return_val_if_fail (file != NULL, 0);
386
387   return file->n_ranges;
388 }
389
390 /**
391  * gst_sparse_file_get_range_before:
392  * @file: a #GstSparseFile
393  * @offset: the range offset
394  * @start: result start
395  * @stop: result stop
396  *
397  * Get the start and stop offset of the range containing data before or
398  * including @offset.
399  *
400  * Returns: %TRUE if the range with data before @offset exists.
401  *
402  * Since: 1.4
403  */
404 gboolean
405 gst_sparse_file_get_range_before (GstSparseFile * file, gsize offset,
406     gsize * start, gsize * stop)
407 {
408   GstSparseRange *walk, *result = NULL;
409
410   g_return_val_if_fail (file != NULL, FALSE);
411
412   for (walk = file->ranges; walk; walk = walk->next) {
413     GST_DEBUG ("start %" G_GSIZE_FORMAT " > %" G_GSIZE_FORMAT,
414         walk->stop, offset);
415     if (walk->start > offset)
416       break;
417
418     if (walk->start <= offset)
419       result = walk;
420   }
421
422   if (result) {
423     if (start)
424       *start = result->start;
425     if (stop)
426       *stop = result->stop;
427   }
428   return result != NULL;
429 }
430
431 /**
432  * gst_sparse_file_get_range_after:
433  * @file: a #GstSparseFile
434  * @offset: the range offset
435  * @start: result start
436  * @stop: result stop
437  *
438  * Get the start and stop offset of the range containing data after or
439  * including @offset.
440  *
441  * Returns: %TRUE if the range with data after @offset exists.
442  *
443  * Since: 1.4
444  */
445 gboolean
446 gst_sparse_file_get_range_after (GstSparseFile * file, gsize offset,
447     gsize * start, gsize * stop)
448 {
449   GstSparseRange *walk, *result = NULL;
450
451   g_return_val_if_fail (file != NULL, FALSE);
452
453   for (walk = file->ranges; walk; walk = walk->next) {
454     GST_DEBUG ("stop %" G_GSIZE_FORMAT " > %" G_GSIZE_FORMAT,
455         walk->stop, offset);
456     if (walk->stop > offset) {
457       result = walk;
458       break;
459     }
460   }
461   if (result) {
462     if (start)
463       *start = result->start;
464     if (stop)
465       *stop = result->stop;
466   }
467   return result != NULL;
468 }