1 /*
2 mkvmerge -- utility for splicing together matroska files
3 from component media subtypes
4
5 Distributed under the GPL v2
6 see the file COPYING for details
7 or visit https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
8
9 IO callback class definitions
10
11 Written by Moritz Bunkus <moritz@bunkus.org>.
12 */
13
14 #include "common/common_pch.h"
15
16 #include <QRegularExpression>
17
18 #include "common/id_info.h"
19 #include "common/mm_io_x.h"
20 #include "common/mm_file_io.h"
21 #include "common/mm_multi_file_io.h"
22 #include "common/mm_multi_file_io_p.h"
23 #include "common/output.h"
24 #include "common/path.h"
25 #include "common/qt.h"
26 #include "common/strings/editing.h"
27 #include "common/strings/parsing.h"
28
mm_multi_file_io_c(std::vector<std::filesystem::path> const & file_names,std::string const & display_file_name)29 mm_multi_file_io_c::mm_multi_file_io_c(std::vector<std::filesystem::path> const &file_names,
30 std::string const &display_file_name)
31 : mm_io_c{*new mm_multi_file_io_private_c{file_names, display_file_name}}
32 {
33 }
34
mm_multi_file_io_c(mm_multi_file_io_private_c & p)35 mm_multi_file_io_c::mm_multi_file_io_c(mm_multi_file_io_private_c &p)
36 : mm_io_c{p}
37 {
38 }
39
~mm_multi_file_io_c()40 mm_multi_file_io_c::~mm_multi_file_io_c() {
41 close_multi_file_io();
42 }
43
44 uint64_t
getFilePointer()45 mm_multi_file_io_c::getFilePointer() {
46 return p_func()->current_pos;
47 }
48
49 void
setFilePointer(int64_t offset,libebml::seek_mode mode)50 mm_multi_file_io_c::setFilePointer(int64_t offset,
51 libebml::seek_mode mode) {
52 auto p = p_func();
53
54 int64_t new_pos
55 = libebml::seek_beginning == mode ? offset
56 : libebml::seek_end == mode ? p->total_size + offset // offsets from the end are negative already
57 : p->current_pos + offset;
58
59 if ((0 > new_pos) || (static_cast<int64_t>(p->total_size) < new_pos))
60 throw mtx::mm_io::seek_x();
61
62 p->current_file = 0;
63 for (auto &file : p->files) {
64 if ((file.global_start + file.size) < static_cast<uint64_t>(new_pos)) {
65 ++p->current_file;
66 continue;
67 }
68
69 p->current_pos = new_pos;
70 p->current_local_pos = new_pos - file.global_start;
71 file.file->setFilePointer(p->current_local_pos);
72 break;
73 }
74 }
75
76 uint32_t
_read(void * buffer,size_t size)77 mm_multi_file_io_c::_read(void *buffer,
78 size_t size) {
79 auto p = p_func();
80
81 size_t num_read_total = 0;
82 auto buffer_ptr = static_cast<unsigned char *>(buffer);
83
84 while (!eof() && (num_read_total < size)) {
85 auto &file = p->files[p->current_file];
86 auto num_to_read = static_cast<uint64_t>(std::min(static_cast<uint64_t>(size) - static_cast<uint64_t>(num_read_total), file.size - p->current_local_pos));
87
88 if (0 != num_to_read) {
89 size_t num_read = file.file->read(buffer_ptr, num_to_read);
90 num_read_total += num_read;
91 buffer_ptr += num_read;
92 p->current_local_pos += num_read;
93 p->current_pos += num_read;
94
95 if (num_read != num_to_read)
96 break;
97 }
98
99 if ((p->current_local_pos >= file.size) && (p->files.size() > (p->current_file + 1))) {
100 ++p->current_file;
101 p->current_local_pos = 0;
102 p->files[p->current_file].file->setFilePointer(0);
103 }
104 }
105
106 return num_read_total;
107 }
108
109 size_t
_write(const void *,size_t)110 mm_multi_file_io_c::_write(const void *,
111 size_t) {
112 throw mtx::mm_io::wrong_read_write_access_x();
113 }
114
115 void
close()116 mm_multi_file_io_c::close() {
117 close_multi_file_io();
118 }
119
120 void
close_multi_file_io()121 mm_multi_file_io_c::close_multi_file_io() {
122 auto p = p_func();
123
124 for (auto &file : p->files)
125 file.file->close();
126
127 p->files.clear();
128 p->total_size = 0;
129 p->current_pos = 0;
130 p->current_local_pos = 0;
131 }
132
133 bool
eof()134 mm_multi_file_io_c::eof() {
135 auto p = p_func();
136
137 return p->files.empty() || ((p->current_file == (p->files.size() - 1)) && (p->current_local_pos >= p->files[p->current_file].size));
138 }
139
140 std::vector<std::filesystem::path>
get_file_names()141 mm_multi_file_io_c::get_file_names() {
142 auto p = p_func();
143
144 std::vector<std::filesystem::path> file_names;
145 for (auto &file : p->files)
146 file_names.push_back(file.file_name);
147
148 return file_names;
149 }
150
151 void
create_verbose_identification_info(mtx::id::info_c & info)152 mm_multi_file_io_c::create_verbose_identification_info(mtx::id::info_c &info) {
153 auto p = p_func();
154 auto file_names = nlohmann::json::array();
155 for (auto &file : p->files)
156 if (file.file_name != p->files.front().file_name)
157 file_names.push_back(file.file_name.u8string());
158
159 info.add(mtx::id::other_file, file_names);
160 }
161
162 void
display_other_file_info()163 mm_multi_file_io_c::display_other_file_info() {
164 auto p = p_func();
165
166 std::stringstream out;
167
168 for (auto &file : p->files)
169 if (file.file_name != p->files.front().file_name) {
170 if (!out.str().empty())
171 out << ", ";
172 out << file.file_name.filename();
173 }
174
175 if (!out.str().empty())
176 mxinfo(fmt::format(Y("'{0}': Processing the following files as well: {1}\n"), p->display_file_name, out.str()));
177 }
178
179 void
enable_buffering(bool enable)180 mm_multi_file_io_c::enable_buffering(bool enable) {
181 auto p = p_func();
182
183 for (auto &file : p->files)
184 file.file->enable_buffering(enable);
185 }
186
187 std::string
get_file_name() const188 mm_multi_file_io_c::get_file_name()
189 const {
190 return p_func()->display_file_name;
191 }
192
193 mm_io_cptr
open_multi(const std::string & display_file_name,bool single_only)194 mm_multi_file_io_c::open_multi(const std::string &display_file_name,
195 bool single_only) {
196 auto first_file_name = mtx::fs::absolute(mtx::fs::to_path(display_file_name));
197 auto base_name = mtx::fs::to_path(first_file_name).stem().u8string();
198 auto extension = balg::to_lower_copy(mtx::fs::to_path(first_file_name).extension().u8string());
199
200 QRegularExpression file_name_re{"(.+[_\\-])(\\d+)$"};
201 auto matches = file_name_re.match(Q(base_name));
202
203 if (!matches.hasMatch() || single_only) {
204 std::vector<std::filesystem::path> file_names;
205 file_names.push_back(first_file_name);
206 return mm_io_cptr(new mm_multi_file_io_c(file_names, display_file_name));
207 }
208
209 int start_number = 1;
210 mtx::string::parse_number(to_utf8(matches.captured(2)), start_number);
211
212 base_name = to_utf8(matches.captured(1).toLower());
213
214 std::vector<std::pair<int, std::filesystem::path>> paths;
215 paths.emplace_back(start_number, first_file_name);
216
217 std::filesystem::directory_iterator end_itr;
218 for (std::filesystem::directory_iterator itr(first_file_name.parent_path()); itr != end_itr; ++itr) {
219 if ( std::filesystem::is_directory(itr->status())
220 || !balg::iequals(itr->path().extension().u8string(), extension))
221 continue;
222
223 auto stem = itr->path().stem();
224 int current_number = 0;
225 matches = file_name_re.match(Q(stem));
226
227 if ( !matches.hasMatch()
228 || (to_utf8(matches.captured(1).toLower()) != base_name)
229 || !mtx::string::parse_number(to_utf8(matches.captured(2)), current_number)
230 || (current_number <= start_number))
231 continue;
232
233 paths.emplace_back(current_number, itr->path());
234 }
235
236 std::sort(paths.begin(), paths.end());
237
238 std::vector<std::filesystem::path> file_names;
239 for (auto &path : paths)
240 file_names.emplace_back(std::get<1>(path));
241
242 return mm_io_cptr(new mm_multi_file_io_c(file_names, display_file_name));
243 }
244