Return AVERROR_EOF from the avio_read method when appropriate.
[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  */
138 FileGroup::Result
139 FileGroup::read (uint8_t* buffer, int amount) const
140 {
141         DCPOMATIC_ASSERT (_current_file);
142
143         int read = 0;
144         while (true) {
145
146                 bool eof = false;
147                 size_t to_read = amount - read;
148
149                 DCPOMATIC_ASSERT (_current_file);
150
151                 auto const current_position = _current_file->tell();
152                 if (current_position == -1) {
153                         to_read = 0;
154                         eof = true;
155                 } else if ((current_position + to_read) > _current_size) {
156                         to_read = _current_size - current_position;
157                         eof = true;
158                 }
159
160                 int const this_time = _current_file->read(buffer + read, 1, to_read);
161                 read += this_time;
162                 _position += this_time;
163                 if (read == amount) {
164                         /* Done */
165                         break;
166                 }
167
168                 if (_current_file->error()) {
169                         throw FileError (String::compose("fread error %1", errno), _paths[_current_path]);
170                 }
171
172                 if (eof) {
173                         /* See if there is another file to use */
174                         if ((_current_path + 1) >= _paths.size()) {
175                                 return { read, true };
176                         }
177                         ensure_open_path (_current_path + 1);
178                 }
179         }
180
181         return { read, false };
182 }
183
184
185 /** @return Combined length of all the files */
186 int64_t
187 FileGroup::length () const
188 {
189         int64_t len = 0;
190         for (size_t i = 0; i < _paths.size(); ++i) {
191                 len += boost::filesystem::file_size (_paths[i]);
192         }
193
194         return len;
195 }