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 <dcp/filesystem.h>
33 #include <sndfile.h>
34 #include <cstdio>
35
36
37 using std::vector;
38
39
40 /** Construct a FileGroup with no files */
41 FileGroup::FileGroup ()
42 {
43
44 }
45
46
47 /** Construct a FileGroup with a single file */
48 FileGroup::FileGroup (boost::filesystem::path p)
49 {
50         _paths.push_back (p);
51         ensure_open_path (0);
52         seek (0, SEEK_SET);
53 }
54
55
56 /** Construct a FileGroup with multiple files */
57 FileGroup::FileGroup (vector<boost::filesystem::path> const & p)
58         : _paths (p)
59 {
60         ensure_open_path (0);
61         seek (0, SEEK_SET);
62 }
63
64
65 void
66 FileGroup::set_paths (vector<boost::filesystem::path> const & p)
67 {
68         _paths = p;
69         ensure_open_path (0);
70         seek (0, SEEK_SET);
71 }
72
73
74 /** Ensure that the given path index in the content is the _current_file */
75 void
76 FileGroup::ensure_open_path (size_t p) const
77 {
78         if (_current_file && _current_path == p) {
79                 /* Already open */
80                 return;
81         }
82
83         if (_current_file) {
84                 _current_file->close();
85         }
86
87         _current_path = p;
88         _current_file = dcp::File(_paths[_current_path], "rb");
89         if (!_current_file) {
90                 throw OpenFileError (_paths[_current_path], errno, OpenFileError::READ);
91         }
92         _current_size = dcp::filesystem::file_size(_paths[_current_path]);
93 }
94
95
96 int64_t
97 FileGroup::seek (int64_t pos, int whence) const
98 {
99         switch (whence) {
100         case SEEK_SET:
101                 _position = pos;
102                 break;
103         case SEEK_CUR:
104                 _position += pos;
105                 break;
106         case SEEK_END:
107                 _position = length() - pos;
108                 break;
109         }
110
111         /* Find an offset within one of the files, if _position is within a file */
112         size_t i = 0;
113         int64_t sub_pos = _position;
114         while (i < _paths.size()) {
115                 boost::uintmax_t len = dcp::filesystem::file_size(_paths[i]);
116                 if (sub_pos < int64_t(len)) {
117                         break;
118                 }
119                 sub_pos -= int64_t(len);
120                 ++i;
121         }
122
123         if (i < _paths.size()) {
124                 ensure_open_path (i);
125                 _current_file->seek(sub_pos, SEEK_SET);
126         } else {
127                 ensure_open_path (_paths.size() - 1);
128                 _current_file->seek(_current_size, SEEK_SET);
129         }
130
131         return _position;
132 }
133
134
135 /** Try to read some data from the current position into a buffer.
136  *  @param buffer Buffer to write data into.
137  *  @param amount Number of bytes to read.
138  */
139 FileGroup::Result
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                                 return { read, true };
177                         }
178                         ensure_open_path (_current_path + 1);
179                 }
180         }
181
182         return { read, false };
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 += dcp::filesystem::file_size(_paths[i]);
193         }
194
195         return len;
196 }