Merge remote-tracking branch 'origin/master' into 2.0
[dcpomatic.git] / src / lib / file_group.cc
1 /*
2     Copyright (C) 2013-2014 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 /** @file  src/lib/file_group.cc
21  *  @brief FileGroup class.
22  */
23
24 #include <cstdio>
25 #include <sndfile.h>
26 #include "file_group.h"
27 #include "exceptions.h"
28 #include "cross.h"
29
30 using std::vector;
31 using std::cout;
32
33 /** Construct a FileGroup with no files */
34 FileGroup::FileGroup ()
35         : _current_path (0)
36         , _current_file (0)
37 {
38
39 }
40
41 /** Construct a FileGroup with a single file */
42 FileGroup::FileGroup (boost::filesystem::path p)
43         : _current_path (0)
44         , _current_file (0)
45 {
46         _paths.push_back (p);
47         ensure_open_path (0);
48         seek (0, SEEK_SET);
49 }
50
51 /** Construct a FileGroup with multiple files */
52 FileGroup::FileGroup (vector<boost::filesystem::path> const & p)
53         : _paths (p)
54         , _current_path (0)
55         , _current_file (0)
56 {
57         ensure_open_path (0);
58         seek (0, SEEK_SET);
59 }
60
61 /** Destroy a FileGroup, closing any open file */
62 FileGroup::~FileGroup ()
63 {
64         if (_current_file) {
65                 fclose (_current_file);
66         }
67 }
68
69 void
70 FileGroup::set_paths (vector<boost::filesystem::path> const & p)
71 {
72         _paths = p;
73         ensure_open_path (0);
74         seek (0, SEEK_SET);
75 }
76
77 /** Ensure that the given path index in the content is the _current_file */
78 void
79 FileGroup::ensure_open_path (size_t p) const
80 {
81         if (_current_file && _current_path == p) {
82                 /* Already open */
83                 return;
84         }
85         
86         if (_current_file) {
87                 fclose (_current_file);
88         }
89
90         _current_path = p;
91         _current_file = fopen_boost (_paths[_current_path], "rb");
92         if (_current_file == 0) {
93                 throw OpenFileError (_paths[_current_path]);
94         }
95 }
96
97 int64_t
98 FileGroup::seek (int64_t pos, int whence) const
99 {
100         /* Convert pos to `full_pos', which is an offset from the start
101            of all the files.
102         */
103         int64_t full_pos = 0;
104         switch (whence) {
105         case SEEK_SET:
106                 full_pos = pos;
107                 break;
108         case SEEK_CUR:
109                 for (size_t i = 0; i < _current_path; ++i) {
110                         full_pos += boost::filesystem::file_size (_paths[i]);
111                 }
112 #ifdef DCPOMATIC_WINDOWS
113                 full_pos += _ftelli64 (_current_file);
114 #else           
115                 full_pos += ftell (_current_file);
116 #endif          
117                 full_pos += pos;
118                 break;
119         case SEEK_END:
120                 full_pos = length() - pos;
121                 break;
122         }
123
124         /* Seek to full_pos */
125         size_t i = 0;
126         int64_t sub_pos = full_pos;
127         while (i < _paths.size ()) {
128                 boost::uintmax_t len = boost::filesystem::file_size (_paths[i]);
129                 if (sub_pos < int64_t (len)) {
130                         break;
131                 }
132                 sub_pos -= len;
133                 ++i;
134         }
135
136         if (i == _paths.size ()) {
137                 return -1;
138         }
139
140         ensure_open_path (i);
141         dcpomatic_fseek (_current_file, sub_pos, SEEK_SET);
142         return full_pos;
143 }
144
145 /** Try to read some data from the current position into a buffer.
146  *  @param buffer Buffer to write data into.
147  *  @param amount Number of bytes to read.
148  *  @return Number of bytes read, or -1 in the case of error.
149  */
150 int
151 FileGroup::read (uint8_t* buffer, int amount) const
152 {
153         int read = 0;
154         while (true) {
155                 int const this_time = fread (buffer + read, 1, amount - read, _current_file);
156                 read += this_time;
157                 if (read == amount) {
158                         /* Done */
159                         break;
160                 }
161
162                 /* See if there is another file to use */
163                 if ((_current_path + 1) >= _paths.size()) {
164                         break;
165                 }
166                 ensure_open_path (_current_path + 1);
167         }
168
169         return read;
170 }
171
172 /** @return Combined length of all the files */
173 int64_t
174 FileGroup::length () const
175 {
176         int64_t len = 0;
177         for (size_t i = 0; i < _paths.size(); ++i) {
178                 len += boost::filesystem::file_size (_paths[i]);
179         }
180
181         return len;
182 }