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