Supporters update.
[dcpomatic.git] / src / lib / file_group.cc
1 /*
2     Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 /** @file  src/lib/file_group.cc
23  *  @brief FileGroup class.
24  */
25
26
27 #include "compose.hpp"
28 #include "cross.h"
29 #include "dcpomatic_assert.h"
30 #include "exceptions.h"
31 #include "file_group.h"
32 #include <sndfile.h>
33 #include <cstdio>
34
35
36 using std::vector;
37
38
39 /** Construct a FileGroup with no files */
40 FileGroup::FileGroup ()
41 {
42
43 }
44
45
46 /** Construct a FileGroup with a single file */
47 FileGroup::FileGroup (boost::filesystem::path p)
48 {
49         _paths.push_back (p);
50         ensure_open_path (0);
51         seek (0, SEEK_SET);
52 }
53
54
55 /** Construct a FileGroup with multiple files */
56 FileGroup::FileGroup (vector<boost::filesystem::path> const & p)
57         : _paths (p)
58 {
59         ensure_open_path (0);
60         seek (0, SEEK_SET);
61 }
62
63
64 void
65 FileGroup::set_paths (vector<boost::filesystem::path> const & p)
66 {
67         _paths = p;
68         ensure_open_path (0);
69         seek (0, SEEK_SET);
70 }
71
72
73 /** Ensure that the given path index in the content is the _current_file */
74 void
75 FileGroup::ensure_open_path (size_t p) const
76 {
77         if (_current_file && _current_path == p) {
78                 /* Already open */
79                 return;
80         }
81
82         if (_current_file) {
83                 _current_file->close();
84         }
85
86         _current_path = p;
87         _current_file = dcp::File(_paths[_current_path], "rb");
88         if (!_current_file) {
89                 throw OpenFileError (_paths[_current_path], errno, OpenFileError::READ);
90         }
91         _current_size = boost::filesystem::file_size (_paths[_current_path]);
92 }
93
94
95 int64_t
96 FileGroup::seek (int64_t pos, int whence) const
97 {
98         switch (whence) {
99         case SEEK_SET:
100                 _position = pos;
101                 break;
102         case SEEK_CUR:
103                 _position += pos;
104                 break;
105         case SEEK_END:
106                 _position = length() - pos;
107                 break;
108         }
109
110         /* Find an offset within one of the files, if _position is within a file */
111         size_t i = 0;
112         int64_t sub_pos = _position;
113         while (i < _paths.size()) {
114                 boost::uintmax_t len = boost::filesystem::file_size (_paths[i]);
115                 if (sub_pos < int64_t(len)) {
116                         break;
117                 }
118                 sub_pos -= int64_t(len);
119                 ++i;
120         }
121
122         if (i < _paths.size()) {
123                 ensure_open_path (i);
124                 _current_file->seek(sub_pos, SEEK_SET);
125         } else {
126                 ensure_open_path (_paths.size() - 1);
127                 _current_file->seek(_current_size, SEEK_SET);
128         }
129
130         return _position;
131 }
132
133
134 /** Try to read some data from the current position into a buffer.
135  *  @param buffer Buffer to write data into.
136  *  @param amount Number of bytes to read.
137  *  @return Number of bytes read.
138  */
139 int
140 FileGroup::read (uint8_t* buffer, int amount) const
141 {
142         DCPOMATIC_ASSERT (_current_file);
143
144         int read = 0;
145         while (true) {
146
147                 bool eof = false;
148                 size_t to_read = amount - read;
149
150                 DCPOMATIC_ASSERT (_current_file);
151
152                 auto const current_position = _current_file->tell();
153                 if (current_position == -1) {
154                         to_read = 0;
155                         eof = true;
156                 } else if ((current_position + to_read) > _current_size) {
157                         to_read = _current_size - current_position;
158                         eof = true;
159                 }
160
161                 int const this_time = _current_file->read(buffer + read, 1, to_read);
162                 read += this_time;
163                 _position += this_time;
164                 if (read == amount) {
165                         /* Done */
166                         break;
167                 }
168
169                 if (_current_file->error()) {
170                         throw FileError (String::compose("fread error %1", errno), _paths[_current_path]);
171                 }
172
173                 if (eof) {
174                         /* See if there is another file to use */
175                         if ((_current_path + 1) >= _paths.size()) {
176                                 break;
177                         }
178                         ensure_open_path (_current_path + 1);
179                 }
180         }
181
182         return read;
183 }
184
185
186 /** @return Combined length of all the files */
187 int64_t
188 FileGroup::length () const
189 {
190         int64_t len = 0;
191         for (size_t i = 0; i < _paths.size(); ++i) {
192                 len += boost::filesystem::file_size (_paths[i]);
193         }
194
195         return len;
196 }