file-buffer.hpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. #pragma once
  2. #include <nall/platform.hpp>
  3. #include <nall/array-span.hpp>
  4. #include <nall/array-view.hpp>
  5. #include <nall/inode.hpp>
  6. #include <nall/range.hpp>
  7. #include <nall/stdint.hpp>
  8. #include <nall/string.hpp>
  9. #include <nall/utility.hpp>
  10. #include <nall/varint.hpp>
  11. #include <nall/hash/sha256.hpp>
  12. namespace nall {
  13. //on Windows (at least for 7 and earlier), FILE* is not buffered
  14. //thus, reading/writing one byte at a time will be dramatically slower
  15. //on all other OSes, FILE* is buffered
  16. //in order to ensure good performance, file_buffer implements its own buffer
  17. //this speeds up Windows substantially, without harming performance elsewhere much
  18. struct file_buffer {
  19. struct mode { enum : uint { read, write, modify, append }; };
  20. struct index { enum : uint { absolute, relative }; };
  21. file_buffer(const file_buffer&) = delete;
  22. auto operator=(const file_buffer&) -> file_buffer& = delete;
  23. file_buffer() = default;
  24. file_buffer(const string& filename, uint mode) { open(filename, mode); }
  25. file_buffer(file_buffer&& source) { operator=(move(source)); }
  26. ~file_buffer() { close(); }
  27. auto operator=(file_buffer&& source) -> file_buffer& {
  28. buffer = source.buffer;
  29. bufferOffset = source.bufferOffset;
  30. bufferDirty = source.bufferDirty;
  31. fileHandle = source.fileHandle;
  32. fileOffset = source.fileOffset;
  33. fileSize = source.fileSize;
  34. fileMode = source.fileMode;
  35. source.bufferOffset = -1;
  36. source.bufferDirty = false;
  37. source.fileHandle = nullptr;
  38. source.fileOffset = 0;
  39. source.fileSize = 0;
  40. source.fileMode = mode::read;
  41. return *this;
  42. }
  43. explicit operator bool() const {
  44. return (bool)fileHandle;
  45. }
  46. auto read() -> uint8_t {
  47. if(!fileHandle) return 0; //file not open
  48. if(fileMode == mode::write) return 0; //reads not permitted
  49. if(fileOffset >= fileSize) return 0; //cannot read past end of file
  50. bufferSynchronize();
  51. return buffer[fileOffset++ & buffer.size() - 1];
  52. }
  53. template<typename T = uint64_t> auto readl(uint length = 1) -> T {
  54. T data = 0;
  55. for(uint n : range(length)) {
  56. data |= (T)read() << n * 8;
  57. }
  58. return data;
  59. }
  60. template<typename T = uint64_t> auto readm(uint length = 1) -> T {
  61. T data = 0;
  62. while(length--) {
  63. data <<= 8;
  64. data |= read();
  65. }
  66. return data;
  67. }
  68. auto reads(uint length) -> string {
  69. string result;
  70. result.resize(length);
  71. for(auto& byte : result) byte = read();
  72. return result;
  73. }
  74. auto read(array_span<uint8_t> memory) -> void {
  75. for(auto& byte : memory) byte = read();
  76. }
  77. auto write(uint8_t data) -> void {
  78. if(!fileHandle) return; //file not open
  79. if(fileMode == mode::read) return; //writes not permitted
  80. bufferSynchronize();
  81. buffer[fileOffset++ & buffer.size() - 1] = data;
  82. bufferDirty = true;
  83. if(fileOffset > fileSize) fileSize = fileOffset;
  84. }
  85. template<typename T = uint64_t> auto writel(T data, uint length = 1) -> void {
  86. while(length--) {
  87. write(uint8_t(data));
  88. data >>= 8;
  89. }
  90. }
  91. template<typename T = uint64_t> auto writem(T data, uint length = 1) -> void {
  92. for(uint n : reverse(range(length))) {
  93. write(uint8_t(data >> n * 8));
  94. }
  95. }
  96. auto writes(const string& s) -> void {
  97. for(auto& byte : s) write(byte);
  98. }
  99. auto write(array_view<uint8_t> memory) -> void {
  100. for(auto& byte : memory) write(byte);
  101. }
  102. template<typename... P> auto print(P&&... p) -> void {
  103. string s{forward<P>(p)...};
  104. for(auto& byte : s) write(byte);
  105. }
  106. auto flush() -> void {
  107. bufferFlush();
  108. fflush(fileHandle);
  109. }
  110. auto seek(int64_t offset, uint index_ = index::absolute) -> void {
  111. if(!fileHandle) return;
  112. bufferFlush();
  113. int64_t seekOffset = fileOffset;
  114. switch(index_) {
  115. case index::absolute: seekOffset = offset; break;
  116. case index::relative: seekOffset += offset; break;
  117. }
  118. if(seekOffset < 0) seekOffset = 0; //cannot seek before start of file
  119. if(seekOffset > fileSize) {
  120. if(fileMode == mode::read) { //cannot seek past end of file
  121. seekOffset = fileSize;
  122. } else { //pad file to requested location
  123. fileOffset = fileSize;
  124. while(fileSize < seekOffset) write(0);
  125. }
  126. }
  127. fileOffset = seekOffset;
  128. }
  129. auto offset() const -> uint64_t {
  130. if(!fileHandle) return 0;
  131. return fileOffset;
  132. }
  133. auto size() const -> uint64_t {
  134. if(!fileHandle) return 0;
  135. return fileSize;
  136. }
  137. auto truncate(uint64_t size) -> bool {
  138. if(!fileHandle) return false;
  139. #if defined(API_POSIX)
  140. return ftruncate(fileno(fileHandle), size) == 0;
  141. #elif defined(API_WINDOWS)
  142. return _chsize(fileno(fileHandle), size) == 0;
  143. #endif
  144. }
  145. auto end() const -> bool {
  146. if(!fileHandle) return true;
  147. return fileOffset >= fileSize;
  148. }
  149. auto open(const string& filename, uint mode_) -> bool {
  150. close();
  151. switch(fileMode = mode_) {
  152. #if defined(API_POSIX)
  153. case mode::read: fileHandle = fopen(filename, "rb" ); break;
  154. case mode::write: fileHandle = fopen(filename, "wb+"); break; //need read permission for buffering
  155. case mode::modify: fileHandle = fopen(filename, "rb+"); break;
  156. case mode::append: fileHandle = fopen(filename, "wb+"); break;
  157. #elif defined(API_WINDOWS)
  158. case mode::read: fileHandle = _wfopen(utf16_t(filename), L"rb" ); break;
  159. case mode::write: fileHandle = _wfopen(utf16_t(filename), L"wb+"); break;
  160. case mode::modify: fileHandle = _wfopen(utf16_t(filename), L"rb+"); break;
  161. case mode::append: fileHandle = _wfopen(utf16_t(filename), L"wb+"); break;
  162. #endif
  163. }
  164. if(!fileHandle) return false;
  165. bufferOffset = -1;
  166. fileOffset = 0;
  167. fseek(fileHandle, 0, SEEK_END);
  168. fileSize = ftell(fileHandle);
  169. fseek(fileHandle, 0, SEEK_SET);
  170. return true;
  171. }
  172. auto close() -> void {
  173. if(!fileHandle) return;
  174. bufferFlush();
  175. fclose(fileHandle);
  176. fileHandle = nullptr;
  177. }
  178. private:
  179. array<uint8_t[4096]> buffer;
  180. int bufferOffset = -1;
  181. bool bufferDirty = false;
  182. FILE* fileHandle = nullptr;
  183. uint64_t fileOffset = 0;
  184. uint64_t fileSize = 0;
  185. uint fileMode = mode::read;
  186. auto bufferSynchronize() -> void {
  187. if(!fileHandle) return;
  188. if(bufferOffset == (fileOffset & ~(buffer.size() - 1))) return;
  189. bufferFlush();
  190. bufferOffset = fileOffset & ~(buffer.size() - 1);
  191. fseek(fileHandle, bufferOffset, SEEK_SET);
  192. uint64_t length = bufferOffset + buffer.size() <= fileSize ? buffer.size() : fileSize & buffer.size() - 1;
  193. if(length) (void)fread(buffer.data(), 1, length, fileHandle);
  194. }
  195. auto bufferFlush() -> void {
  196. if(!fileHandle) return; //file not open
  197. if(fileMode == mode::read) return; //buffer cannot be written to
  198. if(bufferOffset < 0) return; //buffer unused
  199. if(!bufferDirty) return; //buffer unmodified since read
  200. fseek(fileHandle, bufferOffset, SEEK_SET);
  201. uint64_t length = bufferOffset + buffer.size() <= fileSize ? buffer.size() : fileSize & buffer.size() - 1;
  202. if(length) (void)fwrite(buffer.data(), 1, length, fileHandle);
  203. bufferOffset = -1;
  204. bufferDirty = false;
  205. }
  206. };
  207. }