Add FileGroup.
[dcpomatic.git] / src / lib / file_group.cc
1 /*
2     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program 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
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cstdio>
21 extern "C" {
22 #include <libavformat/avio.h>
23 }
24 #include <sndfile.h>
25 #include "file_group.h"
26 #include "exceptions.h"
27
28 using std::vector;
29 using std::cout;
30
31 FileGroup::FileGroup (boost::filesystem::path p)
32         : _current_path (0)
33         , _current_file (0)
34 {
35         _paths.push_back (p);
36         seek (0, SEEK_SET);
37 }
38
39 FileGroup::FileGroup (vector<boost::filesystem::path> const & p)
40         : _paths (p)
41         , _current_path (0)
42         , _current_file (0)
43 {
44         ensure_open_path (0);
45         seek (0, SEEK_SET);
46 }
47
48 FileGroup::~FileGroup ()
49 {
50         if (_current_file) {
51                 fclose (_current_file);
52         }
53 }
54
55
56 /** Ensure that the given path index in the content is the _current_file */
57 void
58 FileGroup::ensure_open_path (size_t p) const
59 {
60         if (_current_file && _current_path == p) {
61                 /* Already open */
62                 return;
63         }
64         
65         if (_current_file) {
66                 fclose (_current_file);
67         }
68
69         _current_path = p;
70         _current_file = fopen (_paths[_current_path].string().c_str(), "rb");
71         if (_current_file == 0) {
72                 throw OpenFileError (_paths[_current_path]);
73         }
74 }
75
76 int64_t
77 FileGroup::seek (int64_t pos, int whence) const
78 {
79         int64_t const len = length ();
80         
81         /* Convert pos to `full_pos', which is an offset from the start
82            of all the files.
83         */
84         int64_t full_pos = 0;
85         switch (whence) {
86         case SEEK_SET:
87                 full_pos = pos;
88                 break;
89         case SEEK_CUR:
90                 for (size_t i = 0; i < _current_path; ++i) {
91                         full_pos += boost::filesystem::file_size (_paths[i]);
92                 }
93                 full_pos += ftell (_current_file);
94                 full_pos += pos;
95                 break;
96         case SEEK_END:
97                 full_pos = len - pos;
98                 break;
99         case AVSEEK_SIZE:
100                 return len;
101         }
102
103         /* Seek to full_pos */
104         size_t i = 0;
105         int64_t sub_pos = full_pos;
106         while (i < _paths.size ()) {
107                 boost::uintmax_t len = boost::filesystem::file_size (_paths[i]);
108                 if (sub_pos < int64_t (len)) {
109                         break;
110                 }
111                 sub_pos -= len;
112                 ++i;
113         }
114
115         if (i == _paths.size ()) {
116                 return -1;
117         }
118
119         ensure_open_path (i);
120         fseek (_current_file, sub_pos, SEEK_SET);
121         return full_pos;
122 }
123
124 /** Try to read some data from the current position into a buffer.
125  *  @param buffer Buffer to write data into.
126  *  @param amount Number of bytes to read.
127  *  @return Number of bytes read, or -1 in the case of error.
128  */
129 int
130 FileGroup::read (uint8_t* buffer, int amount) const
131 {
132         int read = 0;
133         while (1) {
134                 int const this_time = fread (buffer + read, 1, amount - read, _current_file);
135                 read += this_time;
136                 if (read == amount) {
137                         /* Done */
138                         break;
139                 }
140
141                 /* See if there is another file to use */
142                 if ((_current_path + 1) >= _paths.size()) {
143                         break;
144                 }
145                 ensure_open_path (_current_path + 1);
146         }
147
148         return read;
149 }
150
151 int64_t
152 FileGroup::length () const
153 {
154         int64_t len = 0;
155         for (size_t i = 0; i < _paths.size(); ++i) {
156                 len += boost::filesystem::file_size (_paths[i]);
157         }
158
159         return len;
160 }