|
1
|
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 }
|