Add more filesystem wrappers that DoM needs.
[libdcp.git] / src / filesystem.cc
1 /*
2     Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp 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     libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34
35 #include "filesystem.h"
36 #include <boost/algorithm/string.hpp>
37
38
39 bool
40 dcp::filesystem::exists(boost::filesystem::path const& path)
41 {
42         return boost::filesystem::exists(dcp::filesystem::fix_long_path(path));
43 }
44
45
46 bool
47 dcp::filesystem::exists(boost::filesystem::path const& path, boost::system::error_code& ec)
48 {
49         return boost::filesystem::exists(dcp::filesystem::fix_long_path(path), ec);
50 }
51
52
53 bool
54 dcp::filesystem::is_directory(boost::filesystem::path const& path)
55 {
56         return boost::filesystem::is_directory(dcp::filesystem::fix_long_path(path));
57 }
58
59
60 bool
61 dcp::filesystem::is_empty(boost::filesystem::path const& path)
62 {
63         return boost::filesystem::is_empty(dcp::filesystem::fix_long_path(path));
64 }
65
66
67 bool
68 dcp::filesystem::is_regular_file(boost::filesystem::path const& path)
69 {
70         return boost::filesystem::is_regular_file(dcp::filesystem::fix_long_path(path));
71 }
72
73
74 bool
75 dcp::filesystem::create_directory(boost::filesystem::path const& path)
76 {
77         return boost::filesystem::create_directory(dcp::filesystem::fix_long_path(path));
78 }
79
80
81 bool
82 dcp::filesystem::create_directory(boost::filesystem::path const& path, boost::system::error_code& ec)
83 {
84         return boost::filesystem::create_directory(dcp::filesystem::fix_long_path(path), ec);
85 }
86
87
88 void
89 dcp::filesystem::copy(boost::filesystem::path const& from, boost::filesystem::path const& to)
90 {
91         boost::filesystem::copy(dcp::filesystem::fix_long_path(from), dcp::filesystem::fix_long_path(to));
92 }
93
94
95 void
96 dcp::filesystem::copy_file(boost::filesystem::path const& from, boost::filesystem::path const& to)
97 {
98         boost::filesystem::copy_file(dcp::filesystem::fix_long_path(from), dcp::filesystem::fix_long_path(to));
99 }
100
101
102 void
103 dcp::filesystem::copy_file(boost::filesystem::path const& from, boost::filesystem::path const& to, boost::system::error_code& ec)
104 {
105         boost::filesystem::copy_file(dcp::filesystem::fix_long_path(from), dcp::filesystem::fix_long_path(to), ec);
106 }
107
108
109 void
110 dcp::filesystem::copy_file(boost::filesystem::path const& from, boost::filesystem::path const& to, boost::filesystem::copy_option option)
111 {
112         boost::filesystem::copy_file(dcp::filesystem::fix_long_path(from), dcp::filesystem::fix_long_path(to), option);
113 }
114
115
116 bool
117 dcp::filesystem::create_directories(boost::filesystem::path const& path)
118 {
119         return boost::filesystem::create_directories(dcp::filesystem::fix_long_path(path));
120 }
121
122
123 bool
124 dcp::filesystem::create_directories(boost::filesystem::path const& path, boost::system::error_code& ec)
125 {
126         return boost::filesystem::create_directories(dcp::filesystem::fix_long_path(path), ec);
127 }
128
129
130 boost::filesystem::path
131 dcp::filesystem::absolute(boost::filesystem::path const& path)
132 {
133         return dcp::filesystem::unfix_long_path(boost::filesystem::absolute(dcp::filesystem::fix_long_path(path)));
134 }
135
136
137 boost::filesystem::path
138 dcp::filesystem::canonical(boost::filesystem::path const& path)
139 {
140         return dcp::filesystem::unfix_long_path(boost::filesystem::canonical(dcp::filesystem::fix_long_path(path)));
141 }
142
143
144 boost::filesystem::path
145 dcp::filesystem::weakly_canonical(boost::filesystem::path const& path)
146 {
147         return dcp::filesystem::unfix_long_path(boost::filesystem::weakly_canonical(dcp::filesystem::fix_long_path(path)));
148 }
149
150
151 bool
152 dcp::filesystem::remove(boost::filesystem::path const& path)
153 {
154         return boost::filesystem::remove(dcp::filesystem::fix_long_path(path));
155 }
156
157
158 bool
159 dcp::filesystem::remove(boost::filesystem::path const& path, boost::system::error_code& ec)
160 {
161         return boost::filesystem::remove(dcp::filesystem::fix_long_path(path), ec);
162 }
163
164
165 uintmax_t
166 dcp::filesystem::remove_all(boost::filesystem::path const& path)
167 {
168         return boost::filesystem::remove_all(dcp::filesystem::fix_long_path(path));
169 }
170
171
172 uintmax_t
173 dcp::filesystem::remove_all(boost::filesystem::path const& path, boost::system::error_code& ec)
174 {
175         return boost::filesystem::remove_all(dcp::filesystem::fix_long_path(path), ec);
176 }
177
178
179 uintmax_t
180 dcp::filesystem::file_size(boost::filesystem::path const& path)
181 {
182         return boost::filesystem::file_size(dcp::filesystem::fix_long_path(path));
183 }
184
185
186 uintmax_t
187 dcp::filesystem::file_size(boost::filesystem::path const& path, boost::system::error_code& ec)
188 {
189         return boost::filesystem::file_size(dcp::filesystem::fix_long_path(path), ec);
190 }
191
192
193 boost::filesystem::path
194 dcp::filesystem::current_path()
195 {
196         return dcp::filesystem::unfix_long_path(boost::filesystem::current_path());
197 }
198
199
200 void
201 dcp::filesystem::current_path(boost::filesystem::path const& path)
202 {
203         boost::filesystem::current_path(dcp::filesystem::fix_long_path(path));
204 }
205
206
207 void
208 dcp::filesystem::create_hard_link(boost::filesystem::path const& from, boost::filesystem::path const& to)
209 {
210         boost::filesystem::create_hard_link(dcp::filesystem::fix_long_path(from), dcp::filesystem::fix_long_path(to));
211 }
212
213
214 void
215 dcp::filesystem::create_hard_link(boost::filesystem::path const& from, boost::filesystem::path const& to, boost::system::error_code& ec)
216 {
217         boost::filesystem::create_hard_link(dcp::filesystem::fix_long_path(from), dcp::filesystem::fix_long_path(to), ec);
218 }
219
220
221 void
222 dcp::filesystem::create_symlink(boost::filesystem::path const& from, boost::filesystem::path const& to, boost::system::error_code& ec)
223 {
224         boost::filesystem::create_symlink(dcp::filesystem::fix_long_path(from), dcp::filesystem::fix_long_path(to), ec);
225 }
226
227
228 std::string
229 dcp::filesystem::extension(boost::filesystem::path const& path)
230 {
231         return boost::filesystem::extension(dcp::filesystem::fix_long_path(path));
232 }
233
234
235 boost::filesystem::space_info
236 dcp::filesystem::space(boost::filesystem::path const& path)
237 {
238         return boost::filesystem::space(dcp::filesystem::fix_long_path(path));
239 }
240
241
242 std::time_t
243 dcp::filesystem::last_write_time(boost::filesystem::path const& path)
244 {
245         return boost::filesystem::last_write_time(dcp::filesystem::fix_long_path(path));
246 }
247
248
249 std::time_t
250 dcp::filesystem::last_write_time(boost::filesystem::path const& path, boost::system::error_code& ec)
251 {
252         return boost::filesystem::last_write_time(dcp::filesystem::fix_long_path(path), ec);
253 }
254
255
256 uintmax_t
257 dcp::filesystem::hard_link_count(boost::filesystem::path const& path)
258 {
259         return boost::filesystem::hard_link_count(dcp::filesystem::fix_long_path(path));
260 }
261
262
263 void
264 dcp::filesystem::rename(boost::filesystem::path const& old_path, boost::filesystem::path const& new_path)
265 {
266         boost::filesystem::rename(dcp::filesystem::fix_long_path(old_path), dcp::filesystem::fix_long_path(new_path));
267 }
268
269
270 void
271 dcp::filesystem::rename(boost::filesystem::path const& old_path, boost::filesystem::path const& new_path, boost::system::error_code& ec)
272 {
273         boost::filesystem::rename(dcp::filesystem::fix_long_path(old_path), dcp::filesystem::fix_long_path(new_path), ec);
274 }
275
276
277 /* We don't really need this but let's add it for completeness */
278 boost::filesystem::path
279 dcp::filesystem::change_extension(boost::filesystem::path const& path, std::string const& new_extension)
280 {
281         return boost::filesystem::change_extension(path, new_extension);
282 }
283
284
285 #ifdef DCPOMATIC_WINDOWS
286
287 dcp::filesystem::directory_iterator::directory_iterator(boost::filesystem::path const& path)
288         : _wrapped(dcp::filesystem::fix_long_path(path))
289 {
290
291 }
292
293
294 dcp::filesystem::directory_iterator::directory_iterator(boost::filesystem::path const& path, boost::system::error_code& ec)
295         : _wrapped(dcp::filesystem::fix_long_path(path), ec)
296 {
297
298 }
299
300
301 boost::filesystem::path
302 dcp::filesystem::directory_entry::path() const
303 {
304         return dcp::filesystem::unfix_long_path(_path);
305 }
306
307
308 dcp::filesystem::directory_entry::operator boost::filesystem::path const &() const
309 {
310         return dcp::filesystem::unfix_long_path(_path);
311 }
312
313
314 dcp::filesystem::recursive_directory_iterator::recursive_directory_iterator(boost::filesystem::path const& path)
315         : _wrapped(dcp::filesystem::fix_long_path(path))
316 {
317
318 }
319
320 #else
321
322 dcp::filesystem::directory_iterator::directory_iterator(boost::filesystem::path const& path)
323         : _wrapped(path)
324 {
325
326 }
327
328
329 dcp::filesystem::directory_iterator::directory_iterator(boost::filesystem::path const& path, boost::system::error_code& ec)
330         : _wrapped(path, ec)
331 {
332
333 }
334
335
336 boost::filesystem::path
337 dcp::filesystem::directory_entry::path() const
338 {
339         return _path;
340 }
341
342
343 dcp::filesystem::directory_entry::operator boost::filesystem::path const &() const
344 {
345         return _path;
346 }
347
348
349 dcp::filesystem::recursive_directory_iterator::recursive_directory_iterator(boost::filesystem::path const& path)
350         : _wrapped(path)
351 {
352
353 }
354
355 #endif
356
357
358 dcp::filesystem::directory_entry::directory_entry(boost::filesystem::path const& path)
359         : _path(path)
360 {
361
362 }
363
364
365 dcp::filesystem::directory_iterator&
366 dcp::filesystem::directory_iterator::operator++()
367 {
368         ++_wrapped;
369         return *this;
370 }
371
372
373 dcp::filesystem::directory_entry
374 dcp::filesystem::directory_iterator::operator*() const
375 {
376         _entry = dcp::filesystem::directory_entry(*_wrapped);
377         return _entry;
378 }
379
380
381 dcp::filesystem::directory_entry*
382 dcp::filesystem::directory_iterator::operator->() const
383 {
384         _entry = dcp::filesystem::directory_entry(_wrapped->path());
385         return &_entry;
386 }
387
388 bool
389 dcp::filesystem::directory_iterator::operator!=(dcp::filesystem::directory_iterator const& other) const
390 {
391         return _wrapped != other._wrapped;
392 }
393
394
395 dcp::filesystem::directory_iterator const&
396 dcp::filesystem::begin(dcp::filesystem::directory_iterator const& iter)
397 {
398         return iter;
399 }
400
401
402 dcp::filesystem::directory_iterator
403 dcp::filesystem::end(dcp::filesystem::directory_iterator const&)
404 {
405         return dcp::filesystem::directory_iterator();
406 }
407
408
409 dcp::filesystem::recursive_directory_iterator&
410 dcp::filesystem::recursive_directory_iterator::operator++()
411 {
412         ++_wrapped;
413         return *this;
414 }
415
416
417 bool
418 dcp::filesystem::recursive_directory_iterator::operator!=(dcp::filesystem::recursive_directory_iterator const& other) const
419 {
420         return _wrapped != other._wrapped;
421 }
422
423
424 dcp::filesystem::directory_entry
425 dcp::filesystem::recursive_directory_iterator::operator*() const
426 {
427         _entry = dcp::filesystem::directory_entry(_wrapped->path());
428         return _entry;
429 }
430
431
432 dcp::filesystem::directory_entry*
433 dcp::filesystem::recursive_directory_iterator::operator->() const
434 {
435         _entry = dcp::filesystem::directory_entry(_wrapped->path());
436         return &_entry;
437 }
438
439
440 dcp::filesystem::recursive_directory_iterator const&
441 dcp::filesystem::begin(dcp::filesystem::recursive_directory_iterator const& iter)
442 {
443         return iter;
444 }
445
446
447 dcp::filesystem::recursive_directory_iterator
448 dcp::filesystem::end(dcp::filesystem::recursive_directory_iterator const&)
449 {
450         return dcp::filesystem::recursive_directory_iterator();
451 }
452
453
454 /** Windows can't "by default" cope with paths longer than 260 characters, so if you pass such a path to
455  *  any boost::filesystem method it will fail.  There is a "fix" for this, which is to prepend
456  *  the string \\?\ to the path.  This will make it work, so long as:
457  *  - the path is absolute.
458  *  - the path contains no .. parts.
459  *  - the path only uses backslashes.
460  *  - individual path components are "short enough" (probably less than 255 characters)
461  *
462  *  See https://www.boost.org/doc/libs/1_57_0/libs/filesystem/doc/reference.html under
463  *  "Warning: Long paths on Windows" for some details.
464  *
465  *  Our fopen_boost uses this method to get this fix, but any other calls to boost::filesystem
466  *  will not unless this method is explicitly called to pre-process the pathname.
467  */
468 boost::filesystem::path
469 dcp::filesystem::fix_long_path(boost::filesystem::path long_path)
470 {
471 #ifdef LIBDCP_WINDOWS
472         using namespace boost::filesystem;
473
474         if (boost::algorithm::starts_with(long_path.string(), "\\\\")) {
475                 /* This could mean it starts with \\ (i.e. a SMB path) or \\?\ (a long path)
476                  * or a variety of other things... anyway, we'll leave it alone.
477                  */
478                 return long_path;
479         }
480
481         /* We have to make the path canonical but we can't call canonical() on the long path
482          * as it will fail.  So we'll sort of do it ourselves (possibly badly).
483          */
484         path fixed = "\\\\?\\";
485         if (long_path.is_absolute()) {
486                 fixed += long_path.make_preferred();
487         } else {
488                 fixed += filesystem::current_path() / long_path.make_preferred();
489         }
490         return fixed;
491 #else
492         return long_path;
493 #endif
494 }
495
496
497 boost::filesystem::path
498 dcp::filesystem::unfix_long_path(boost::filesystem::path long_path)
499 {
500 #ifdef LIBDCP_WINDOWS
501         if (boost::algorithm::starts_with(long_path.string(), "\\\\?\\")) {
502                 return long_path.string().substr(4);
503         }
504 #endif
505         return long_path;
506 }