Mercurial > foo_out_sdl
comparison foosdk/sdk/foobar2000/SDK/file_cached_impl.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.h" | |
| 3 namespace { | |
| 4 | |
| 5 #define FILE_CACHED_DEBUG_LOG 0 | |
| 6 | |
| 7 class file_cached_impl_v2 : public service_multi_inherit< file_v2, service_multi_inherit< file_cached, file_lowLevelIO > > { | |
| 8 public: | |
| 9 enum {minBlockSize = 4096}; | |
| 10 enum {maxSkipSize = 128*1024}; | |
| 11 file_cached_impl_v2(size_t maxBlockSize) : m_maxBlockSize(maxBlockSize) { | |
| 12 //m_buffer.set_size(blocksize); | |
| 13 } | |
| 14 size_t get_cache_block_size() override {return m_maxBlockSize;} | |
| 15 void suggest_grow_cache(size_t suggestSize) override { | |
| 16 if (m_maxBlockSize < suggestSize) m_maxBlockSize = suggestSize; | |
| 17 } | |
| 18 | |
| 19 void initialize(service_ptr_t<file> p_base,abort_callback & p_abort) { | |
| 20 m_base = p_base; | |
| 21 m_can_seek = m_base->can_seek(); | |
| 22 _reinit(p_abort); | |
| 23 } | |
| 24 t_filestats2 get_stats2(uint32_t f, abort_callback& a) override { | |
| 25 flush_buffer(); | |
| 26 return m_base->get_stats2_(f, a); | |
| 27 } | |
| 28 size_t lowLevelIO(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) override { | |
| 29 abort.check(); | |
| 30 file_lowLevelIO::ptr ll; | |
| 31 if ( ll &= m_base ) { | |
| 32 flush_buffer(); | |
| 33 return ll->lowLevelIO(guid, arg1, arg2, arg2size, abort ); | |
| 34 } | |
| 35 return 0; | |
| 36 } | |
| 37 private: | |
| 38 void _reinit(abort_callback & p_abort) { | |
| 39 m_position = 0; | |
| 40 | |
| 41 if (m_can_seek) { | |
| 42 m_position_base = m_base->get_position(p_abort); | |
| 43 } else { | |
| 44 m_position_base = 0; | |
| 45 } | |
| 46 | |
| 47 m_size = m_base->get_size(p_abort); | |
| 48 | |
| 49 flush_buffer(); | |
| 50 } | |
| 51 public: | |
| 52 | |
| 53 t_filesize skip(t_filesize p_bytes,abort_callback & p_abort) override { | |
| 54 if (p_bytes > maxSkipSize) { | |
| 55 const t_filesize size = get_size(p_abort); | |
| 56 if (size != filesize_invalid) { | |
| 57 const t_filesize position = get_position(p_abort); | |
| 58 const t_filesize toskip = pfc::min_t( p_bytes, size - position ); | |
| 59 seek(position + toskip,p_abort); | |
| 60 return toskip; | |
| 61 } | |
| 62 } | |
| 63 return skip_( p_bytes, p_abort ); | |
| 64 } | |
| 65 t_filesize skip_(t_filesize p_bytes,abort_callback & p_abort) { | |
| 66 #if FILE_CACHED_DEBUG_LOG | |
| 67 FB2K_DebugLog() << "Skipping bytes: " << p_bytes; | |
| 68 #endif | |
| 69 t_filesize todo = p_bytes; | |
| 70 for(;;) { | |
| 71 size_t inBuffer = this->bufferRemaining(); | |
| 72 size_t delta = (size_t) pfc::min_t<t_filesize>(inBuffer, todo); | |
| 73 m_bufferReadPtr += delta; | |
| 74 m_position += delta; | |
| 75 todo -= delta; | |
| 76 if (todo == 0) break; | |
| 77 p_abort.check(); | |
| 78 this->m_bufferState = 0; // null it early to leave in a consistent state if base read fails | |
| 79 this->m_bufferReadPtr = 0; | |
| 80 baseSeek(m_position,p_abort); | |
| 81 m_readSize = pfc::min_t<size_t>(m_readSize << 1, this->m_maxBlockSize); | |
| 82 if (m_readSize < minBlockSize) m_readSize = minBlockSize; | |
| 83 #if FILE_CACHED_DEBUG_LOG | |
| 84 FB2K_DebugLog() << "Growing read size: " << m_readSize; | |
| 85 #endif | |
| 86 m_buffer.grow_size(m_readSize); | |
| 87 m_bufferState = m_base->read(m_buffer.get_ptr(), m_readSize, p_abort); | |
| 88 if (m_bufferState == 0) break; | |
| 89 m_position_base += m_bufferState; | |
| 90 } | |
| 91 | |
| 92 return p_bytes - todo; | |
| 93 } | |
| 94 | |
| 95 t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) override { | |
| 96 #if FILE_CACHED_DEBUG_LOG | |
| 97 FB2K_DebugLog() << "Reading bytes: " << p_bytes; | |
| 98 #endif | |
| 99 t_uint8 * outptr = (t_uint8*)p_buffer; | |
| 100 size_t todo = p_bytes; | |
| 101 for(;;) { | |
| 102 size_t inBuffer = this->bufferRemaining(); | |
| 103 size_t delta = pfc::min_t<size_t>(inBuffer, todo); | |
| 104 memcpy(outptr, this->m_buffer.get_ptr() + m_bufferReadPtr, delta); | |
| 105 m_bufferReadPtr += delta; | |
| 106 m_position += delta; | |
| 107 todo -= delta; | |
| 108 if (todo == 0) break; | |
| 109 p_abort.check(); | |
| 110 outptr += delta; | |
| 111 this->m_bufferState = 0; // null it early to leave in a consistent state if base read fails | |
| 112 this->m_bufferReadPtr = 0; | |
| 113 baseSeek(m_position,p_abort); | |
| 114 m_readSize = pfc::min_t<size_t>(m_readSize << 1, this->m_maxBlockSize); | |
| 115 if (m_readSize < minBlockSize) m_readSize = minBlockSize; | |
| 116 #if FILE_CACHED_DEBUG_LOG | |
| 117 FB2K_DebugLog() << "Growing read size: " << m_readSize; | |
| 118 #endif | |
| 119 m_buffer.grow_size(m_readSize); | |
| 120 m_bufferState = m_base->read(m_buffer.get_ptr(), m_readSize, p_abort); | |
| 121 if (m_bufferState == 0) break; | |
| 122 m_position_base += m_bufferState; | |
| 123 } | |
| 124 | |
| 125 return p_bytes - todo; | |
| 126 } | |
| 127 | |
| 128 void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) override { | |
| 129 #if FILE_CACHED_DEBUG_LOG | |
| 130 FB2K_DebugLog() << "Writing bytes: " << p_bytes; | |
| 131 #endif | |
| 132 p_abort.check(); | |
| 133 baseSeek(m_position,p_abort); | |
| 134 m_base->write(p_buffer,p_bytes,p_abort); | |
| 135 m_position_base = m_position = m_position + p_bytes; | |
| 136 if (m_size < m_position) m_size = m_position; | |
| 137 flush_buffer(); | |
| 138 } | |
| 139 | |
| 140 t_filesize get_size(abort_callback & p_abort) override { | |
| 141 p_abort.check(); | |
| 142 return m_size; | |
| 143 } | |
| 144 t_filesize get_position(abort_callback & p_abort) override { | |
| 145 p_abort.check(); | |
| 146 PFC_ASSERT( m_position <= m_size ); | |
| 147 return m_position; | |
| 148 } | |
| 149 void set_eof(abort_callback & p_abort) { | |
| 150 p_abort.check(); | |
| 151 baseSeek(m_position,p_abort); | |
| 152 m_base->set_eof(p_abort); | |
| 153 flush_buffer(); | |
| 154 } | |
| 155 void seek(t_filesize p_position,abort_callback & p_abort) override { | |
| 156 #if FILE_CACHED_DEBUG_LOG | |
| 157 FB2K_DebugLog() << "Seeking: " << p_position; | |
| 158 #endif | |
| 159 p_abort.check(); | |
| 160 if (!m_can_seek) throw exception_io_object_not_seekable(); | |
| 161 if (p_position > m_size) throw exception_io_seek_out_of_range(); | |
| 162 int64_t delta = p_position - m_position; | |
| 163 | |
| 164 // special case | |
| 165 if (delta >= 0 && delta <= this->minBlockSize) { | |
| 166 #if FILE_CACHED_DEBUG_LOG | |
| 167 FB2K_DebugLog() << "Skip-seeking: " << p_position; | |
| 168 #endif | |
| 169 t_filesize skipped = this->skip_( delta, p_abort ); | |
| 170 PFC_ASSERT( skipped == (t_filesize)delta ); (void) skipped; | |
| 171 return; | |
| 172 } | |
| 173 | |
| 174 m_position = p_position; | |
| 175 // within currently buffered data? | |
| 176 if ((delta >= 0 && (uint64_t) delta <= bufferRemaining()) || (delta < 0 && (uint64_t)(-delta) <= m_bufferReadPtr)) { | |
| 177 #if FILE_CACHED_DEBUG_LOG | |
| 178 FB2K_DebugLog() << "Quick-seeking: " << p_position; | |
| 179 #endif | |
| 180 m_bufferReadPtr += (ptrdiff_t)delta; | |
| 181 } else { | |
| 182 #if FILE_CACHED_DEBUG_LOG | |
| 183 FB2K_DebugLog() << "Slow-seeking: " << p_position; | |
| 184 #endif | |
| 185 this->flush_buffer(); | |
| 186 } | |
| 187 } | |
| 188 void reopen(abort_callback & p_abort) override { | |
| 189 if (this->m_can_seek) { | |
| 190 seek(0,p_abort); | |
| 191 } else { | |
| 192 this->m_base->reopen( p_abort ); | |
| 193 this->_reinit( p_abort ); | |
| 194 } | |
| 195 } | |
| 196 bool can_seek() override {return m_can_seek;} | |
| 197 bool get_content_type(pfc::string_base & out) override {return m_base->get_content_type(out);} | |
| 198 void on_idle(abort_callback & p_abort) override {p_abort.check();m_base->on_idle(p_abort);} | |
| 199 t_filetimestamp get_timestamp(abort_callback & p_abort) override {p_abort.check(); return m_base->get_timestamp(p_abort);} | |
| 200 bool is_remote() override {return m_base->is_remote();} | |
| 201 void resize(t_filesize p_size,abort_callback & p_abort) override { | |
| 202 flush_buffer(); | |
| 203 m_base->resize(p_size,p_abort); | |
| 204 m_size = p_size; | |
| 205 if (m_position > m_size) m_position = m_size; | |
| 206 if (m_position_base > m_size) m_position_base = m_size; | |
| 207 } | |
| 208 private: | |
| 209 size_t bufferRemaining() const {return m_bufferState - m_bufferReadPtr;} | |
| 210 void baseSeek(t_filesize p_target,abort_callback & p_abort) { | |
| 211 if (p_target != m_position_base) { | |
| 212 m_base->seek(p_target,p_abort); | |
| 213 m_position_base = p_target; | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 void flush_buffer() { | |
| 218 m_bufferState = m_bufferReadPtr = 0; | |
| 219 m_readSize = 0; | |
| 220 } | |
| 221 | |
| 222 service_ptr_t<file> m_base; | |
| 223 t_filesize m_position,m_position_base,m_size; | |
| 224 bool m_can_seek; | |
| 225 size_t m_bufferState, m_bufferReadPtr; | |
| 226 pfc::array_t<t_uint8> m_buffer; | |
| 227 size_t m_maxBlockSize; | |
| 228 size_t m_readSize; | |
| 229 }; | |
| 230 | |
| 231 class file_cached_impl : public service_multi_inherit< file_v2, service_multi_inherit< file_cached, file_lowLevelIO > > { | |
| 232 public: | |
| 233 file_cached_impl(t_size blocksize) { | |
| 234 m_buffer.set_size(blocksize); | |
| 235 } | |
| 236 size_t get_cache_block_size() override {return m_buffer.get_size();} | |
| 237 void suggest_grow_cache(size_t) override {} | |
| 238 void initialize(service_ptr_t<file> p_base,abort_callback & p_abort) { | |
| 239 m_base = p_base; | |
| 240 m_can_seek = m_base->can_seek(); | |
| 241 _reinit(p_abort); | |
| 242 } | |
| 243 private: | |
| 244 void _reinit(abort_callback & p_abort) { | |
| 245 m_position = 0; | |
| 246 | |
| 247 if (m_can_seek) { | |
| 248 m_position_base = m_base->get_position(p_abort); | |
| 249 } else { | |
| 250 m_position_base = 0; | |
| 251 } | |
| 252 | |
| 253 m_size = m_base->get_size(p_abort); | |
| 254 | |
| 255 flush_buffer(); | |
| 256 } | |
| 257 public: | |
| 258 t_filestats2 get_stats2(uint32_t f, abort_callback& a) override { | |
| 259 flush_buffer(); | |
| 260 return m_base->get_stats2_(f, a); | |
| 261 } | |
| 262 size_t lowLevelIO(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) override { | |
| 263 abort.check(); | |
| 264 file_lowLevelIO::ptr ll; | |
| 265 if ( ll &= m_base ) { | |
| 266 flush_buffer(); | |
| 267 return ll->lowLevelIO(guid, arg1, arg2, arg2size, abort); | |
| 268 } | |
| 269 return 0; | |
| 270 } | |
| 271 t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) override { | |
| 272 t_uint8 * outptr = (t_uint8*)p_buffer; | |
| 273 t_size done = 0; | |
| 274 while(done < p_bytes && m_position < m_size) { | |
| 275 p_abort.check(); | |
| 276 | |
| 277 if (m_position >= m_buffer_position && m_position < m_buffer_position + m_buffer_status) { | |
| 278 t_size delta = pfc::min_t<t_size>((t_size)(m_buffer_position + m_buffer_status - m_position),p_bytes - done); | |
| 279 t_size bufptr = (t_size)(m_position - m_buffer_position); | |
| 280 memcpy(outptr+done,m_buffer.get_ptr()+bufptr,delta); | |
| 281 done += delta; | |
| 282 m_position += delta; | |
| 283 if (m_buffer_status != m_buffer.get_size() && done < p_bytes) break;//EOF before m_size is hit | |
| 284 } else { | |
| 285 m_buffer_position = m_position - m_position % m_buffer.get_size(); | |
| 286 baseSeek(m_buffer_position,p_abort); | |
| 287 | |
| 288 m_buffer_status = m_base->read(m_buffer.get_ptr(),m_buffer.get_size(),p_abort); | |
| 289 m_position_base += m_buffer_status; | |
| 290 | |
| 291 if (m_buffer_status <= (t_size)(m_position - m_buffer_position)) break; | |
| 292 } | |
| 293 } | |
| 294 | |
| 295 return done; | |
| 296 } | |
| 297 | |
| 298 void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) override { | |
| 299 p_abort.check(); | |
| 300 baseSeek(m_position,p_abort); | |
| 301 m_base->write(p_buffer,p_bytes,p_abort); | |
| 302 m_position_base = m_position = m_position + p_bytes; | |
| 303 if (m_size < m_position) m_size = m_position; | |
| 304 flush_buffer(); | |
| 305 } | |
| 306 | |
| 307 t_filesize get_size(abort_callback & p_abort) override { | |
| 308 p_abort.check(); | |
| 309 return m_size; | |
| 310 } | |
| 311 t_filesize get_position(abort_callback & p_abort) override { | |
| 312 p_abort.check(); | |
| 313 return m_position; | |
| 314 } | |
| 315 void set_eof(abort_callback & p_abort) { | |
| 316 p_abort.check(); | |
| 317 baseSeek(m_position,p_abort); | |
| 318 m_base->set_eof(p_abort); | |
| 319 flush_buffer(); | |
| 320 } | |
| 321 void seek(t_filesize p_position,abort_callback & p_abort) override { | |
| 322 p_abort.check(); | |
| 323 if (!m_can_seek) throw exception_io_object_not_seekable(); | |
| 324 if (p_position > m_size) throw exception_io_seek_out_of_range(); | |
| 325 m_position = p_position; | |
| 326 } | |
| 327 void reopen(abort_callback & p_abort) override { | |
| 328 if (this->m_can_seek) { | |
| 329 seek(0,p_abort); | |
| 330 } else { | |
| 331 this->m_base->reopen( p_abort ); | |
| 332 this->_reinit( p_abort ); | |
| 333 } | |
| 334 } | |
| 335 bool can_seek() override {return m_can_seek;} | |
| 336 bool get_content_type(pfc::string_base & out) override {return m_base->get_content_type(out);} | |
| 337 void on_idle(abort_callback & p_abort) override {p_abort.check();m_base->on_idle(p_abort);} | |
| 338 t_filetimestamp get_timestamp(abort_callback & p_abort) override {p_abort.check(); return m_base->get_timestamp(p_abort);} | |
| 339 bool is_remote() override {return m_base->is_remote();} | |
| 340 void resize(t_filesize p_size,abort_callback & p_abort) override { | |
| 341 flush_buffer(); | |
| 342 m_base->resize(p_size,p_abort); | |
| 343 m_size = p_size; | |
| 344 if (m_position > m_size) m_position = m_size; | |
| 345 if (m_position_base > m_size) m_position_base = m_size; | |
| 346 } | |
| 347 private: | |
| 348 void baseSeek(t_filesize p_target,abort_callback & p_abort) { | |
| 349 if (p_target != m_position_base) { | |
| 350 m_base->seek(p_target,p_abort); | |
| 351 m_position_base = p_target; | |
| 352 } | |
| 353 } | |
| 354 | |
| 355 void flush_buffer() { | |
| 356 m_buffer_status = 0; | |
| 357 m_buffer_position = 0; | |
| 358 } | |
| 359 | |
| 360 service_ptr_t<file> m_base; | |
| 361 t_filesize m_position,m_position_base,m_size; | |
| 362 bool m_can_seek; | |
| 363 t_filesize m_buffer_position; | |
| 364 t_size m_buffer_status; | |
| 365 pfc::array_t<t_uint8> m_buffer; | |
| 366 }; | |
| 367 | |
| 368 } | |
| 369 | |
| 370 file::ptr file_cached::g_create(service_ptr_t<file> p_base,abort_callback & p_abort, t_size blockSize) { | |
| 371 | |
| 372 if (p_base->is_in_memory()) { | |
| 373 return p_base; // do not want | |
| 374 } | |
| 375 | |
| 376 { // do not duplicate cache layers, check if the file we're being handed isn't already cached | |
| 377 file_cached::ptr c; | |
| 378 if (p_base->service_query_t(c)) { | |
| 379 c->suggest_grow_cache(blockSize); | |
| 380 return p_base; | |
| 381 } | |
| 382 } | |
| 383 | |
| 384 auto obj = fb2k::service_new< file_cached_impl_v2 >(blockSize); | |
| 385 obj->initialize(p_base,p_abort); | |
| 386 file_v2* asdf = obj.get_ptr(); | |
| 387 return asdf; | |
| 388 } | |
| 389 | |
| 390 void file_cached::g_create(service_ptr_t<file> & p_out,service_ptr_t<file> p_base,abort_callback & p_abort, t_size blockSize) { | |
| 391 p_out = g_create(p_base, p_abort, blockSize); | |
| 392 } | |
| 393 | |
| 394 void file_cached::g_decodeInitCache(file::ptr & theFile, abort_callback & abort, size_t blockSize) { | |
| 395 if (theFile->is_remote() || !theFile->can_seek()) return; | |
| 396 | |
| 397 g_create(theFile, theFile, abort, blockSize); | |
| 398 } |
