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