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