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 }