comparison foosdk/sdk/foobar2000/SDK/filesystem_helper.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
1 #include "foobar2000-sdk-pch.h"
2 #include "filesystem_helper.h"
3
4 void stream_writer_chunk::write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
5 t_size remaining = p_bytes, written = 0;
6 while(remaining > 0) {
7 t_size delta = sizeof(m_buffer) - m_buffer_state;
8 if (delta > remaining) delta = remaining;
9 memcpy(m_buffer,(const t_uint8*)p_buffer + written,delta);
10 written += delta;
11 remaining -= delta;
12
13 if (m_buffer_state == sizeof(m_buffer)) {
14 m_writer->write_lendian_t((t_uint8)m_buffer_state,p_abort);
15 m_writer->write_object(m_buffer,m_buffer_state,p_abort);
16 m_buffer_state = 0;
17 }
18 }
19 }
20
21 void stream_writer_chunk::flush(abort_callback & p_abort)
22 {
23 m_writer->write_lendian_t((t_uint8)m_buffer_state,p_abort);
24 if (m_buffer_state > 0) {
25 m_writer->write_object(m_buffer,m_buffer_state,p_abort);
26 m_buffer_state = 0;
27 }
28 }
29
30 /*
31 stream_writer * m_writer;
32 unsigned m_buffer_state;
33 unsigned char m_buffer[255];
34 */
35
36 t_size stream_reader_chunk::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort)
37 {
38 t_size todo = p_bytes, done = 0;
39 while(todo > 0) {
40 if (m_buffer_size == m_buffer_state) {
41 if (m_eof) break;
42 t_uint8 temp;
43 m_reader->read_lendian_t(temp,p_abort);
44 m_buffer_size = temp;
45 if (temp != sizeof(m_buffer)) m_eof = true;
46 m_buffer_state = 0;
47 if (m_buffer_size>0) {
48 m_reader->read_object(m_buffer,m_buffer_size,p_abort);
49 }
50 }
51
52
53 t_size delta = m_buffer_size - m_buffer_state;
54 if (delta > todo) delta = todo;
55 if (delta > 0) {
56 memcpy((unsigned char*)p_buffer + done,m_buffer + m_buffer_state,delta);
57 todo -= delta;
58 done += delta;
59 m_buffer_state += delta;
60 }
61 }
62 return done;
63 }
64
65 void stream_reader_chunk::flush(abort_callback & p_abort) {
66 while(!m_eof) {
67 p_abort.check_e();
68 t_uint8 temp;
69 m_reader->read_lendian_t(temp,p_abort);
70 m_buffer_size = temp;
71 if (temp != sizeof(m_buffer)) m_eof = true;
72 m_buffer_state = 0;
73 if (m_buffer_size>0) {
74 m_reader->skip_object(m_buffer_size,p_abort);
75 }
76 }
77 }
78
79 /*
80 stream_reader * m_reader;
81 unsigned m_buffer_state, m_buffer_size;
82 bool m_eof;
83 unsigned char m_buffer[255];
84 */
85
86 void stream_reader_chunk::g_skip(stream_reader * p_stream,abort_callback & p_abort) {
87 stream_reader_chunk(p_stream).flush(p_abort);
88 }
89
90
91
92 static void fileSanitySeek(file::ptr f, pfc::array_t<uint8_t> const & content, size_t offset, abort_callback & aborter) {
93 const size_t readAmount = 64 * 1024;
94 pfc::array_staticsize_t<uint8_t> buf; buf.set_size_discard(readAmount);
95 f->seek(offset, aborter);
96 t_filesize positionGot = f->get_position(aborter);
97 if (positionGot != offset) {
98 FB2K_console_formatter() << "File sanity: at " << offset << " reported position became " << positionGot;
99 throw std::runtime_error("Seek test failure");
100 }
101 size_t did = f->read(buf.get_ptr(), readAmount, aborter);
102 size_t expected = pfc::min_t<size_t>(readAmount, content.get_size() - offset);
103 if (expected != did) {
104 FB2K_console_formatter() << "File sanity: at " << offset << " bytes, expected read size of " << expected << ", got " << did;
105 if (did > expected) FB2K_console_formatter() << "Read past EOF";
106 else FB2K_console_formatter() << "Premature EOF";
107 throw std::runtime_error("Seek test failure");
108 }
109 if (memcmp(buf.get_ptr(), content.get_ptr() + offset, did) != 0) {
110 FB2K_console_formatter() << "File sanity: data mismatch at " << offset << " - " << (offset + did) << " bytes";
111 throw std::runtime_error("Seek test failure");
112 }
113 positionGot = f->get_position(aborter);
114 if (positionGot != offset + did) {
115 FB2K_console_formatter() << "File sanity: at " << offset << "+" << did << "=" << (offset + did) << " reported position became " << positionGot;
116 throw std::runtime_error("Seek test failure");
117 }
118 }
119
120 bool fb2kFileSelfTest(file::ptr f, abort_callback & aborter) {
121 try {
122 pfc::array_t<uint8_t> fileContent;
123 f->reopen(aborter);
124 f->read_till_eof(fileContent, aborter);
125
126 {
127 t_filesize sizeClaimed = f->get_size(aborter);
128 if (sizeClaimed == filesize_invalid) {
129 FB2K_console_formatter() << "File sanity: file reports unknown size, actual size read is " << fileContent.get_size();
130 }
131 else {
132 if (sizeClaimed != fileContent.get_size()) {
133 FB2K_console_formatter() << "File sanity: file reports size of " << sizeClaimed << ", actual size read is " << fileContent.get_size();
134 throw std::runtime_error("File size mismatch");
135 }
136 else {
137 FB2K_console_formatter() << "File sanity: file size check OK: " << sizeClaimed;
138 }
139 }
140 }
141
142 {
143 FB2K_console_formatter() << "File sanity: testing N-first-bytes reads...";
144 const size_t sizeUpTo = pfc::min_t<size_t>(fileContent.get_size(), 1024 * 1024);
145 pfc::array_staticsize_t<uint8_t> buf1;
146 buf1.set_size_discard(sizeUpTo);
147
148 for (size_t w = 1; w <= sizeUpTo; w <<= 1) {
149 f->reopen(aborter);
150 size_t did = f->read(buf1.get_ptr(), w, aborter);
151 if (did != w) {
152 FB2K_console_formatter() << "File sanity: premature EOF reading first " << w << " bytes, got " << did;
153 throw std::runtime_error("Premature EOF");
154 }
155 if (memcmp(fileContent.get_ptr(), buf1.get_ptr(), did) != 0) {
156 FB2K_console_formatter() << "File sanity: file content mismatch reading first " << w << " bytes";
157 throw std::runtime_error("File content mismatch");
158 }
159 }
160 }
161 if (f->can_seek()) {
162 FB2K_console_formatter() << "File sanity: testing random access...";
163
164 {
165 size_t sizeUpTo = pfc::min_t<size_t>(fileContent.get_size(), 1024 * 1024);
166 for (size_t w = 1; w < sizeUpTo; w <<= 1) {
167 fileSanitySeek(f, fileContent, w, aborter);
168 }
169 fileSanitySeek(f, fileContent, fileContent.get_size(), aborter);
170 for (size_t w = 1; w < sizeUpTo; w <<= 1) {
171 fileSanitySeek(f, fileContent, fileContent.get_size() - w, aborter);
172 }
173 fileSanitySeek(f, fileContent, fileContent.get_size() / 2, aborter);
174 }
175 }
176 FB2K_console_formatter() << "File sanity test: all OK";
177 return true;
178 }
179 catch (std::exception const & e) {
180 FB2K_console_formatter() << "File sanity test failure: " << e.what();
181 return false;
182 }
183 }
184
185
186 namespace foobar2000_io {
187 void retryFileDelete(double timeout, abort_callback & a, std::function<void()> f) {
188 FB2K_RETRY_ON_EXCEPTION3(f(), a, timeout, exception_io_sharing_violation, exception_io_denied, exception_io_directory_not_empty);
189 }
190 void retryFileMove(double timeout, abort_callback & a, std::function<void() > f) {
191 FB2K_RETRY_FILE_MOVE( f(), a, timeout );
192 }
193 void retryOnSharingViolation(double timeout, abort_callback & a, std::function<void() > f) {
194 FB2K_RETRY_ON_SHARING_VIOLATION(f(), a, timeout);
195 }
196 void retryOnSharingViolation(std::function<void() > f, double timeout, abort_callback & a) {
197 FB2K_RETRY_ON_SHARING_VIOLATION( f(), a, timeout );
198 }
199 void listDirectory( const char * path, abort_callback & aborter, listDirectoryFunc_t func) {
200 listDirectoryCallbackImpl cb; cb.m_func = func;
201 filesystem::g_list_directory(path, cb, aborter);
202 }
203
204 pfc::string8 stripParentFolders( const char * inPath ) {
205 PFC_ASSERT( strstr(inPath, "://" ) == nullptr || matchProtocol( inPath, "file" ) );
206
207 size_t prefixLen = pfc::string_find_first(inPath, "://");
208 if ( prefixLen != pfc_infinite ) prefixLen += 3;
209 else prefixLen = 0;
210
211 pfc::chain_list_v2_t<pfc::string_part_ref> segments;
212
213 const char separator =
214 #ifdef _WIN32
215 '\\'
216 #else
217 '/'
218 #endif
219 ;
220 const char strSeparator[] = {separator, 0};
221
222 pfc::splitStringByChar(segments, inPath + prefixLen, separator );
223 for ( auto i = segments.first(); i.is_valid(); ) {
224 auto n = i; ++n;
225 if ( i->equals( "." ) ) {
226 segments.remove_single( i );
227 } else if ( i->equals( ".." ) ) {
228 auto p = i; --p;
229 if ( p.is_valid() ) segments.remove_single( p );
230 segments.remove_single( i );
231 }
232 i = n;
233 }
234 pfc::string8 ret;
235 if ( prefixLen > 0 ) ret.add_string( inPath, prefixLen );
236 bool bFirst = true;
237 for ( auto i = segments.first(); i.is_valid(); ++ i ) {
238 if (!bFirst) ret << strSeparator;
239 ret << *i;
240 bFirst = false;
241 }
242 return ret;
243 }
244 #ifdef _WIN32
245 pfc::string8 winGetVolumePath(const char * fb2kPath) {
246 PFC_ASSERT(matchProtocol(fb2kPath, "file"));
247 pfc::string8 native;
248 if (!filesystem::g_get_native_path(fb2kPath, native)) throw pfc::exception_invalid_params();
249
250 TCHAR outBuffer[MAX_PATH+1] = {};
251 WIN32_IO_OP( GetVolumePathName( pfc::stringcvt::string_os_from_utf8( native ), outBuffer, MAX_PATH ) );
252 return pfc::stringcvt::string_utf8_from_os( outBuffer ).get_ptr();
253 }
254
255 DWORD winVolumeFlags( const char * fb2kPath ) {
256 PFC_ASSERT(matchProtocol(fb2kPath, "file"));
257 pfc::string8 native;
258 if (!filesystem::g_get_native_path(fb2kPath, native)) throw pfc::exception_invalid_params();
259
260 TCHAR outBuffer[MAX_PATH + 1] = {};
261 WIN32_IO_OP(GetVolumePathName(pfc::stringcvt::string_os_from_utf8(native), outBuffer, MAX_PATH));
262
263 DWORD flags = 0;
264 WIN32_IO_OP(GetVolumeInformation(outBuffer, nullptr, 0, nullptr, nullptr, &flags, nullptr, 0));
265 return flags;
266 }
267 #endif
268 }
269
270 pfc::string8 file_path_canonical(const char* src) {
271 pfc::string8 ret;
272 filesystem::g_get_canonical_path(src, ret);
273 return ret;
274 }
275
276 pfc::string8 file_path_display(const char* src) {
277 pfc::string8 ret;
278 filesystem::g_get_display_path(src, ret);
279 return ret;
280 }
281
282 pfc::string8 fb2k::filename_ext( const char * path, filesystem::ptr & fs) {
283 if ( fs.is_empty() || ! fs->is_our_path( path ) ) {
284 fs = filesystem::tryGet( path );
285 }
286 if ( fs.is_valid() ) return fs->extract_filename_ext( path );
287 // UGLY FALLBACK
288 return pfc::string_filename_ext( path );
289
290 }
291 pfc::string8 fb2k::filename_ext( const char * path ) {
292 filesystem::ptr no_reuse;
293 return filename_ext( path, no_reuse );
294 }
295
296 pfc::string8 fb2k::filename( const char * path ) {
297 return pfc::remove_ext_v2( filename_ext( path ) );
298 }