2 * Copyright (C) 2014 Wim Taymans <wtaymans@redhat.com>
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.
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.
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.
25 #include <glib/gstdio.h>
28 #include "gstsparsefile.h"
31 #include <io.h> /* lseek, open, close, read */
33 #define lseek _lseeki64
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)
45 #define FSEEK_FILE(file,offset) (fseek (file, offset, SEEK_SET) != 0)
49 typedef struct _GstSparseRange GstSparseRange;
51 struct _GstSparseRange
59 #define RANGE_CONTAINS(r,o) ((r)->start <= (o) && (r)->stop > (o))
67 GstSparseRange *ranges;
70 GstSparseRange *write_range;
71 GstSparseRange *read_range;
74 static GstSparseRange *
75 get_write_range (GstSparseFile * file, gsize offset)
77 GstSparseRange *next, *prev, *result = NULL;
79 if (file->write_range && file->write_range->stop == offset)
80 return file->write_range;
85 if (next->start > offset)
88 if (next->stop >= offset) {
96 result = g_slice_new0 (GstSparseRange);
97 result->start = offset;
98 result->stop = offset;
104 file->ranges = result;
106 file->write_range = result;
107 file->read_range = NULL;
114 static GstSparseRange *
115 get_read_range (GstSparseFile * file, gsize offset, gsize count)
117 GstSparseRange *walk, *result = NULL;
119 if (file->read_range && RANGE_CONTAINS (file->read_range, offset))
120 return file->read_range;
122 for (walk = file->ranges; walk; walk = walk->next) {
123 if (walk->start > offset)
126 if (walk->stop >= offset + count) {
135 * gst_sparse_file_new:
137 * Make a new #GstSparseFile backed by the file represented with @fd.
139 * Returns: a new #GstSparseFile, gst_sparse_file_free() after usage.
144 gst_sparse_file_new (void)
146 GstSparseFile *result;
148 result = g_slice_new0 (GstSparseFile);
149 result->current_pos = 0;
150 result->ranges = NULL;
151 result->n_ranges = 0;
157 * gst_sparse_file_set_fd:
158 * @file: a #GstSparseFile
159 * @fd: a file descriptor
161 * Store the data for @file in the file represented with @fd.
163 * Returns: %TRUE when @fd could be set
168 gst_sparse_file_set_fd (GstSparseFile * file, gint fd)
170 g_return_val_if_fail (file != NULL, FALSE);
171 g_return_val_if_fail (fd != 0, FALSE);
173 file->file = fdopen (fd, "wb+");
176 return file->file != NULL;
180 * gst_sparse_file_clear:
181 * @file: a #GstSparseFile
183 * Clear all the ranges in @file.
186 gst_sparse_file_clear (GstSparseFile * file)
188 g_return_if_fail (file != NULL);
192 file->file = fdopen (file->fd, "wb+");
194 g_slice_free_chain (GstSparseRange, file->ranges, next);
195 file->current_pos = 0;
201 * gst_sparse_file_free:
202 * @file: a #GstSparseFile
204 * Free the memory used by @file.
209 gst_sparse_file_free (GstSparseFile * file)
211 g_return_if_fail (file != NULL);
217 g_slice_free_chain (GstSparseRange, file->ranges, next);
218 g_slice_free (GstSparseFile, file);
222 * gst_sparse_file_write:
223 * @file: a #GstSparseFile
224 * @offset: the offset
226 * @count: amount of bytes
227 * @available: amount of bytes already available
230 * Write @count bytes from @data to @file at @offset.
232 * If @available is not %NULL, it will be updated with the amount of
233 * data already available after the last written byte.
235 * Returns: The number of bytes written of 0 on error.
240 gst_sparse_file_write (GstSparseFile * file, gsize offset, gconstpointer data,
241 gsize count, gsize * available, GError ** error)
243 GstSparseRange *range, *next;
246 g_return_val_if_fail (file != NULL, 0);
247 g_return_val_if_fail (count != 0, 0);
250 if (file->current_pos != offset) {
251 GST_DEBUG ("seeking to %" G_GSIZE_FORMAT, offset);
252 if (FSEEK_FILE (file->file, offset))
255 if (fwrite (data, count, 1, file->file) != 1)
259 file->current_pos = offset + count;
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);
266 /* see if we can merge with next region */
267 while ((next = range->next)) {
268 if (next->start > range->stop)
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);
275 range->stop = MAX (next->stop, range->stop);
276 range->next = next->next;
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);
286 *available = range->stop - stop;
293 g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
294 "Error writing file: %s", g_strerror (errno));
300 * gst_sparse_file_read:
301 * @file: a #GstSparseFile
302 * @offset: the offset
304 * @count: amount of bytes
305 * @remaining: amount of bytes remaining
308 * Read @count bytes from @file at @offset into @data.
310 * On error, @error will be set. If there are no @count bytes available
311 * at @offset, %G_IO_ERROR_WOULD_BLOCK is returned.
313 * @remaining will be set to the amount of bytes remaining in the read
316 * Returns: The number of bytes read of 0 on error.
321 gst_sparse_file_read (GstSparseFile * file, gsize offset, gpointer data,
322 gsize count, gsize * remaining, GError ** error)
324 GstSparseRange *range;
327 g_return_val_if_fail (file != NULL, 0);
328 g_return_val_if_fail (count != 0, 0);
330 if ((range = get_read_range (file, offset, count)) == NULL)
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))
340 res = fread (data, 1, count, file->file);
343 file->current_pos = offset + res;
345 if (G_UNLIKELY (res < count))
349 *remaining = range->stop - file->current_pos;
356 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
357 "Offset not written to file yet");
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)) {
373 * gst_sparse_file_n_ranges:
374 * @file: a #GstSparseFile
376 * Get the number of ranges that are written in @file.
378 * Returns: the number of written ranges.
383 gst_sparse_file_n_ranges (GstSparseFile * file)
385 g_return_val_if_fail (file != NULL, 0);
387 return file->n_ranges;
391 * gst_sparse_file_get_range_before:
392 * @file: a #GstSparseFile
393 * @offset: the range offset
394 * @start: result start
397 * Get the start and stop offset of the range containing data before or
400 * Returns: %TRUE if the range with data before @offset exists.
405 gst_sparse_file_get_range_before (GstSparseFile * file, gsize offset,
406 gsize * start, gsize * stop)
408 GstSparseRange *walk, *result = NULL;
410 g_return_val_if_fail (file != NULL, FALSE);
412 for (walk = file->ranges; walk; walk = walk->next) {
413 GST_DEBUG ("start %" G_GSIZE_FORMAT " > %" G_GSIZE_FORMAT,
415 if (walk->start > offset)
418 if (walk->start <= offset)
424 *start = result->start;
426 *stop = result->stop;
428 return result != NULL;
432 * gst_sparse_file_get_range_after:
433 * @file: a #GstSparseFile
434 * @offset: the range offset
435 * @start: result start
438 * Get the start and stop offset of the range containing data after or
441 * Returns: %TRUE if the range with data after @offset exists.
446 gst_sparse_file_get_range_after (GstSparseFile * file, gsize offset,
447 gsize * start, gsize * stop)
449 GstSparseRange *walk, *result = NULL;
451 g_return_val_if_fail (file != NULL, FALSE);
453 for (walk = file->ranges; walk; walk = walk->next) {
454 GST_DEBUG ("stop %" G_GSIZE_FORMAT " > %" G_GSIZE_FORMAT,
456 if (walk->stop > offset) {
463 *start = result->start;
465 *stop = result->stop;
467 return result != NULL;