Add FileGroup.
authorCarl Hetherington <cth@carlh.net>
Sat, 23 Nov 2013 12:33:13 +0000 (12:33 +0000)
committerCarl Hetherington <cth@carlh.net>
Sat, 23 Nov 2013 12:33:13 +0000 (12:33 +0000)
src/lib/file_group.cc [new file with mode: 0644]
src/lib/file_group.h [new file with mode: 0644]
test/file_group_test.cc [new file with mode: 0644]

diff --git a/src/lib/file_group.cc b/src/lib/file_group.cc
new file mode 100644 (file)
index 0000000..5f9531f
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cstdio>
+extern "C" {
+#include <libavformat/avio.h>
+}
+#include <sndfile.h>
+#include "file_group.h"
+#include "exceptions.h"
+
+using std::vector;
+using std::cout;
+
+FileGroup::FileGroup (boost::filesystem::path p)
+       : _current_path (0)
+       , _current_file (0)
+{
+       _paths.push_back (p);
+       seek (0, SEEK_SET);
+}
+
+FileGroup::FileGroup (vector<boost::filesystem::path> const & p)
+       : _paths (p)
+       , _current_path (0)
+       , _current_file (0)
+{
+       ensure_open_path (0);
+       seek (0, SEEK_SET);
+}
+
+FileGroup::~FileGroup ()
+{
+       if (_current_file) {
+               fclose (_current_file);
+       }
+}
+
+
+/** Ensure that the given path index in the content is the _current_file */
+void
+FileGroup::ensure_open_path (size_t p) const
+{
+       if (_current_file && _current_path == p) {
+               /* Already open */
+               return;
+       }
+       
+       if (_current_file) {
+               fclose (_current_file);
+       }
+
+       _current_path = p;
+       _current_file = fopen (_paths[_current_path].string().c_str(), "rb");
+       if (_current_file == 0) {
+               throw OpenFileError (_paths[_current_path]);
+       }
+}
+
+int64_t
+FileGroup::seek (int64_t pos, int whence) const
+{
+       int64_t const len = length ();
+       
+       /* Convert pos to `full_pos', which is an offset from the start
+          of all the files.
+       */
+       int64_t full_pos = 0;
+       switch (whence) {
+       case SEEK_SET:
+               full_pos = pos;
+               break;
+       case SEEK_CUR:
+               for (size_t i = 0; i < _current_path; ++i) {
+                       full_pos += boost::filesystem::file_size (_paths[i]);
+               }
+               full_pos += ftell (_current_file);
+               full_pos += pos;
+               break;
+       case SEEK_END:
+               full_pos = len - pos;
+               break;
+       case AVSEEK_SIZE:
+               return len;
+       }
+
+       /* Seek to full_pos */
+       size_t i = 0;
+       int64_t sub_pos = full_pos;
+       while (i < _paths.size ()) {
+               boost::uintmax_t len = boost::filesystem::file_size (_paths[i]);
+               if (sub_pos < int64_t (len)) {
+                       break;
+               }
+               sub_pos -= len;
+               ++i;
+       }
+
+       if (i == _paths.size ()) {
+               return -1;
+       }
+
+       ensure_open_path (i);
+       fseek (_current_file, sub_pos, SEEK_SET);
+       return full_pos;
+}
+
+/** Try to read some data from the current position into a buffer.
+ *  @param buffer Buffer to write data into.
+ *  @param amount Number of bytes to read.
+ *  @return Number of bytes read, or -1 in the case of error.
+ */
+int
+FileGroup::read (uint8_t* buffer, int amount) const
+{
+       int read = 0;
+       while (1) {
+               int const this_time = fread (buffer + read, 1, amount - read, _current_file);
+               read += this_time;
+               if (read == amount) {
+                       /* Done */
+                       break;
+               }
+
+               /* See if there is another file to use */
+               if ((_current_path + 1) >= _paths.size()) {
+                       break;
+               }
+               ensure_open_path (_current_path + 1);
+       }
+
+       return read;
+}
+
+int64_t
+FileGroup::length () const
+{
+       int64_t len = 0;
+       for (size_t i = 0; i < _paths.size(); ++i) {
+               len += boost::filesystem::file_size (_paths[i]);
+       }
+
+       return len;
+}
diff --git a/src/lib/file_group.h b/src/lib/file_group.h
new file mode 100644 (file)
index 0000000..99803d3
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_FILE_GROUP_H
+#define DCPOMATIC_FILE_GROUP_H
+
+#include <vector>
+#include <boost/filesystem.hpp>
+
+class FileGroup
+{
+public:
+       FileGroup (boost::filesystem::path);
+       FileGroup (std::vector<boost::filesystem::path> const &);
+       ~FileGroup ();
+
+       int64_t seek (int64_t, int) const;
+       int read (uint8_t*, int) const;
+       int64_t length () const;
+
+private:
+       void ensure_open_path (size_t) const;
+
+       std::vector<boost::filesystem::path> _paths;
+       /** Index of path that we are currently reading from */
+       mutable size_t _current_path;
+       mutable FILE* _current_file;
+};
+
+#endif
diff --git a/test/file_group_test.cc b/test/file_group_test.cc
new file mode 100644 (file)
index 0000000..025d6be
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdint.h>
+#include <cstdio>
+#include <boost/test/unit_test.hpp>
+#include <boost/filesystem.hpp>
+extern "C" {
+#include <libavformat/avio.h>
+}
+#include "lib/file_group.h"
+
+using std::vector;
+
+BOOST_AUTO_TEST_CASE (file_group_test)
+{
+       /* Random data; must be big enough for all the files */
+       uint8_t data[65536];
+       for (int i = 0; i < 65536; ++i) {
+               data[i] = rand() & 0xff;
+       }
+       
+       int const num_files = 4;
+
+       int length[] = {
+               99,
+               18941,
+               33110,
+               42
+       };
+
+       int total_length = 0;
+       for (int i = 0; i < num_files; ++i) {
+               total_length += length[i];
+       }
+
+       vector<boost::filesystem::path> name;
+       boost::filesystem::create_directories ("build/test/file_group_test");
+       name.push_back ("build/test/file_group_test/A");
+       name.push_back ("build/test/file_group_test/B");
+       name.push_back ("build/test/file_group_test/C");
+       name.push_back ("build/test/file_group_test/D");
+
+       int base = 0;
+       for (int i = 0; i < num_files; ++i) {
+               FILE* f = fopen (name[i].string().c_str(), "wb");
+               fwrite (data + base, 1, length[i], f);
+               fclose (f);
+               base += length[i];
+       }
+
+       FileGroup fg;
+       fg.set_paths (name);
+       uint8_t test[65536];
+
+       int pos = 0;
+
+       /* Basic read from 0 */
+       BOOST_CHECK_EQUAL (fg.read (test, 64), 64);
+       BOOST_CHECK_EQUAL (memcmp (data, test, 64), 0);
+       pos += 64;
+
+       /* Another read following the previous */
+       BOOST_CHECK_EQUAL (fg.read (test, 4), 4);
+       BOOST_CHECK_EQUAL (memcmp (data + pos, test, 4), 0);
+       pos += 4;
+
+       /* Read overlapping A and B */
+       BOOST_CHECK_EQUAL (fg.read (test, 128), 128);
+       BOOST_CHECK_EQUAL (memcmp (data + pos, test, 128), 0);
+       pos += 128;
+
+       /* Read overlapping B/C/D and over-reading */
+       BOOST_CHECK_EQUAL (fg.read (test, total_length * 3), total_length - pos);
+       BOOST_CHECK_EQUAL (memcmp (data + pos, test, total_length - pos), 0);
+
+       /* Bad seek */
+       BOOST_CHECK_EQUAL (fg.seek (total_length * 2, SEEK_SET), -1);
+
+       /* SEEK_SET */
+       BOOST_CHECK_EQUAL (fg.seek (999, SEEK_SET), 999);
+       BOOST_CHECK_EQUAL (fg.read (test, 64), 64);
+       BOOST_CHECK_EQUAL (memcmp (data + 999, test, 64), 0);
+
+       /* SEEK_CUR */
+       BOOST_CHECK_EQUAL (fg.seek (42, SEEK_CUR), 999 + 64 + 42);
+       BOOST_CHECK_EQUAL (fg.read (test, 64), 64);
+       BOOST_CHECK_EQUAL (memcmp (data + 999 + 64 + 42, test, 64), 0);
+
+       /* SEEK_END */
+       BOOST_CHECK_EQUAL (fg.seek (1077, SEEK_END), total_length - 1077);
+       BOOST_CHECK_EQUAL (fg.read (test, 256), 256);
+       BOOST_CHECK_EQUAL (memcmp (data + total_length - 1077, test, 256), 0);
+
+       /* AVSEEK_SIZE */
+       BOOST_CHECK_EQUAL (fg.seek (-1, AVSEEK_SIZE), total_length);
+}