mptFileIO.h 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. /*
  2. * mptFileIO.h
  3. * -----------
  4. * Purpose: A wrapper around std::fstream, enforcing usage of mpt::PathString.
  5. * Notes : You should only ever use these wrappers instead of plain std::fstream classes.
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #pragma once
  10. #include "openmpt/all/BuildSettings.hpp"
  11. #if defined(MPT_ENABLE_FILEIO)
  12. #include "mpt/io_read/filecursor_memory.hpp"
  13. #include "mpt/io_read/filecursor_stdstream.hpp"
  14. #include "../common/mptString.h"
  15. #include "../common/mptPathString.h"
  16. #include "../common/FileReaderFwd.h"
  17. #if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
  18. #if MPT_GCC_AT_LEAST(9,1,0)
  19. #include <filesystem>
  20. #endif // MPT_GCC_AT_LEAST(9,1,0)
  21. #endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
  22. #include <fstream>
  23. #include <ios>
  24. #include <ostream>
  25. #include <streambuf>
  26. #include <utility>
  27. #if MPT_COMPILER_MSVC
  28. #include <cstdio>
  29. #endif // !MPT_COMPILER_MSVC
  30. #ifdef MODPLUG_TRACKER
  31. #if MPT_OS_WINDOWS
  32. #include <windows.h>
  33. #endif // MPT_OS_WINDOWS
  34. #endif // MODPLUG_TRACKER
  35. #endif // MPT_ENABLE_FILEIO
  36. OPENMPT_NAMESPACE_BEGIN
  37. #if defined(MPT_ENABLE_FILEIO)
  38. // Sets the NTFS compression attribute on the file or directory.
  39. // Requires read and write permissions for already opened files.
  40. // Returns true if the attribute has been set.
  41. // In almost all cases, the return value should be ignored because most filesystems other than NTFS do not support compression.
  42. #ifdef MODPLUG_TRACKER
  43. #if MPT_OS_WINDOWS
  44. bool SetFilesystemCompression(HANDLE hFile);
  45. bool SetFilesystemCompression(int fd);
  46. bool SetFilesystemCompression(const mpt::PathString &filename);
  47. #endif // MPT_OS_WINDOWS
  48. #endif // MODPLUG_TRACKER
  49. namespace mpt
  50. {
  51. namespace detail
  52. {
  53. template<typename Tbase>
  54. inline void fstream_open(Tbase & base, const mpt::PathString & filename, std::ios_base::openmode mode)
  55. {
  56. #if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
  57. #if MPT_GCC_AT_LEAST(9,1,0)
  58. base.open(static_cast<std::filesystem::path>(filename.AsNative()), mode);
  59. #else // !MPT_GCC_AT_LEAST(9,1,0)
  60. // Warning: MinGW with GCC earlier than 9.1 detected. Standard library does neither provide std::fstream wchar_t overloads nor std::filesystem with wchar_t support. Unicode filename support is thus unavailable.
  61. base.open(mpt::ToCharset(mpt::Charset::Locale, filename.AsNative()).c_str(), mode);
  62. #endif // MPT_GCC_AT_LEAST(9,1,0)
  63. #else // !MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
  64. base.open(filename.AsNativePrefixed().c_str(), mode);
  65. #endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
  66. }
  67. } // namespace detail
  68. // We cannot rely on implicit conversion of mpt::PathString to std::filesystem::path when constructing std::fstream
  69. // because of broken overload implementation in GCC libstdc++ 8, 9, 10.
  70. // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95642
  71. // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90704
  72. class fstream
  73. : public std::fstream
  74. {
  75. private:
  76. typedef std::fstream Tbase;
  77. public:
  78. fstream() {}
  79. fstream(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
  80. {
  81. detail::fstream_open<Tbase>(*this, filename, mode);
  82. }
  83. #if MPT_COMPILER_MSVC
  84. protected:
  85. fstream(std::FILE * file)
  86. : std::fstream(file)
  87. {
  88. }
  89. #endif // MPT_COMPILER_MSVC
  90. public:
  91. void open(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
  92. {
  93. detail::fstream_open<Tbase>(*this, filename, mode);
  94. }
  95. void open(const char * filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
  96. void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
  97. #if MPT_OS_WINDOWS
  98. void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
  99. void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
  100. #endif
  101. };
  102. class ifstream
  103. : public std::ifstream
  104. {
  105. private:
  106. typedef std::ifstream Tbase;
  107. public:
  108. ifstream() {}
  109. ifstream(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::in)
  110. {
  111. detail::fstream_open<Tbase>(*this, filename, mode);
  112. }
  113. #if MPT_COMPILER_MSVC
  114. protected:
  115. ifstream(std::FILE * file)
  116. : std::ifstream(file)
  117. {
  118. }
  119. #endif // MPT_COMPILER_MSVC
  120. public:
  121. void open(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::in)
  122. {
  123. detail::fstream_open<Tbase>(*this, filename, mode);
  124. }
  125. void open(const char * filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
  126. void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
  127. #if MPT_OS_WINDOWS
  128. void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
  129. void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
  130. #endif
  131. };
  132. class ofstream
  133. : public std::ofstream
  134. {
  135. private:
  136. typedef std::ofstream Tbase;
  137. public:
  138. ofstream() {}
  139. ofstream(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::out)
  140. {
  141. detail::fstream_open<Tbase>(*this, filename, mode);
  142. }
  143. #if MPT_COMPILER_MSVC
  144. protected:
  145. ofstream(std::FILE * file)
  146. : std::ofstream(file)
  147. {
  148. }
  149. #endif // MPT_COMPILER_MSVC
  150. public:
  151. void open(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::out)
  152. {
  153. detail::fstream_open<Tbase>(*this, filename, mode);
  154. }
  155. void open(const char * filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
  156. void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
  157. #if MPT_OS_WINDOWS
  158. void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
  159. void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
  160. #endif
  161. };
  162. enum class FlushMode
  163. {
  164. None = 0, // no explicit flushes at all
  165. Single = 1, // explicitly flush higher-leverl API layers
  166. Full = 2, // explicitly flush *all* layers, up to and including disk write caches
  167. };
  168. inline FlushMode FlushModeFromBool(bool flush)
  169. {
  170. return flush ? FlushMode::Full : FlushMode::None;
  171. }
  172. #ifdef MODPLUG_TRACKER
  173. class SafeOutputFile
  174. {
  175. private:
  176. FlushMode m_FlushMode;
  177. #if MPT_COMPILER_MSVC
  178. std::FILE *m_f = nullptr;
  179. #else // !MPT_COMPILER_MSVC
  180. mpt::ofstream m_s;
  181. #endif // MPT_COMPILER_MSVC
  182. #if MPT_COMPILER_MSVC
  183. class FILEostream
  184. : public mpt::ofstream
  185. {
  186. public:
  187. FILEostream(std::FILE * file)
  188. : mpt::ofstream(file)
  189. {
  190. return;
  191. }
  192. };
  193. FILEostream m_s;
  194. static mpt::tstring convert_mode(std::ios_base::openmode mode, FlushMode flushMode);
  195. std::FILE * internal_fopen(const mpt::PathString &filename, std::ios_base::openmode mode, FlushMode flushMode);
  196. #endif // MPT_COMPILER_MSVC
  197. public:
  198. SafeOutputFile() = delete;
  199. explicit SafeOutputFile(const mpt::PathString &filename, std::ios_base::openmode mode = std::ios_base::out, FlushMode flushMode = FlushMode::Full)
  200. : m_FlushMode(flushMode)
  201. #if MPT_COMPILER_MSVC
  202. , m_s(internal_fopen(filename, mode | std::ios_base::out, flushMode))
  203. #else // !MPT_COMPILER_MSVC
  204. , m_s(filename, mode)
  205. #endif // MPT_COMPILER_MSVC
  206. {
  207. if(!stream().is_open())
  208. {
  209. stream().setstate(mpt::ofstream::failbit);
  210. }
  211. }
  212. mpt::ofstream& stream()
  213. {
  214. return m_s;
  215. }
  216. operator mpt::ofstream& ()
  217. {
  218. return stream();
  219. }
  220. const mpt::ofstream& stream() const
  221. {
  222. return m_s;
  223. }
  224. operator const mpt::ofstream& () const
  225. {
  226. return stream();
  227. }
  228. operator bool() const
  229. {
  230. return stream() ? true : false;
  231. }
  232. bool operator!() const
  233. {
  234. return stream().operator!();
  235. }
  236. ~SafeOutputFile() noexcept(false);
  237. };
  238. #endif // MODPLUG_TRACKER
  239. #ifdef MODPLUG_TRACKER
  240. // LazyFileRef is a simple reference to an on-disk file by the means of a
  241. // filename which allows easy assignment of the whole file contents to and from
  242. // byte buffers.
  243. class LazyFileRef {
  244. private:
  245. const mpt::PathString m_Filename;
  246. public:
  247. LazyFileRef(const mpt::PathString &filename)
  248. : m_Filename(filename)
  249. {
  250. return;
  251. }
  252. public:
  253. LazyFileRef & operator = (const std::vector<std::byte> &data);
  254. LazyFileRef & operator = (const std::vector<char> &data);
  255. LazyFileRef & operator = (const std::string &data);
  256. operator std::vector<std::byte> () const;
  257. operator std::vector<char> () const;
  258. operator std::string () const;
  259. };
  260. #endif // MODPLUG_TRACKER
  261. } // namespace mpt
  262. class InputFile
  263. {
  264. private:
  265. mpt::PathString m_Filename;
  266. mpt::ifstream m_File;
  267. bool m_IsValid;
  268. bool m_IsCached;
  269. std::vector<std::byte> m_Cache;
  270. public:
  271. InputFile(const mpt::PathString &filename, bool allowWholeFileCaching = false);
  272. ~InputFile();
  273. bool IsValid() const;
  274. bool IsCached() const;
  275. mpt::PathString GetFilename() const;
  276. std::istream& GetStream();
  277. mpt::const_byte_span GetCache();
  278. private:
  279. bool Open(const mpt::PathString &filename, bool allowWholeFileCaching = false);
  280. };
  281. template <typename Targ1>
  282. inline FileCursor make_FileCursor(Targ1 &&arg1)
  283. {
  284. return mpt::IO::make_FileCursor<mpt::PathString>(std::forward<Targ1>(arg1));
  285. }
  286. template <typename Targ1, typename Targ2>
  287. inline FileCursor make_FileCursor(Targ1 &&arg1, Targ2 &&arg2)
  288. {
  289. return mpt::IO::make_FileCursor<mpt::PathString>(std::forward<Targ1>(arg1), std::forward<Targ2>(arg2));
  290. }
  291. // templated in order to reduce header inter-dependencies
  292. class InputFile;
  293. template <typename TInputFile, std::enable_if_t<std::is_same<TInputFile, InputFile>::value, bool> = true>
  294. inline FileCursor make_FileCursor(TInputFile &file)
  295. {
  296. if(!file.IsValid())
  297. {
  298. return FileCursor();
  299. }
  300. if(file.IsCached())
  301. {
  302. return mpt::IO::make_FileCursor<mpt::PathString>(file.GetCache(), std::make_shared<mpt::PathString>(file.GetFilename()));
  303. } else
  304. {
  305. return mpt::IO::make_FileCursor<mpt::PathString>(file.GetStream(), std::make_shared<mpt::PathString>(file.GetFilename()));
  306. }
  307. }
  308. template <typename Targ1>
  309. inline FileCursor GetFileReader(Targ1 &&arg1)
  310. {
  311. return make_FileCursor(std::forward<Targ1>(arg1));
  312. }
  313. template <typename Targ1, typename Targ2>
  314. inline FileCursor GetFileReader(Targ1 &&arg1, Targ2 &&arg2)
  315. {
  316. return make_FileCursor(std::forward<Targ1>(arg1), std::forward<Targ2>(arg2));
  317. }
  318. #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
  319. class OnDiskFileWrapper
  320. {
  321. private:
  322. mpt::PathString m_Filename;
  323. bool m_IsTempFile;
  324. public:
  325. OnDiskFileWrapper(FileCursor& file, const mpt::PathString& fileNameExtension = P_("tmp"));
  326. ~OnDiskFileWrapper();
  327. public:
  328. bool IsValid() const;
  329. mpt::PathString GetFilename() const;
  330. }; // class OnDiskFileWrapper
  331. #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
  332. #endif // MPT_ENABLE_FILEIO
  333. OPENMPT_NAMESPACE_END