foobar2000 SDK  2015-01-14
filesystem.cpp
Go to the documentation of this file.
1 #include "foobar2000.h"
2 
3 static const char unpack_prefix[] = "unpack://";
4 static const unsigned unpack_prefix_len = 9;
5 
7 {
10  if (e.first(ptr)) do {
11  p->reopen(p_abort);
12  try {
13  ptr->open(p_out,p,p_abort);
14  return;
15  } catch(exception_io_data const &) {}
16  } while(e.next(ptr));
17  throw exception_io_data();
18 }
19 
20 void file::seek_probe(t_filesize p_position, abort_callback & p_abort) {
21  try { seek(p_position, p_abort); } catch(exception_io_seek_out_of_range) {throw exception_io_data();}
22 }
23 
24 void file::seek_ex(t_sfilesize p_position, file::t_seek_mode p_mode, abort_callback &p_abort) {
25  switch(p_mode) {
26  case seek_from_beginning:
27  seek(p_position,p_abort);
28  break;
29  case seek_from_current:
30  seek(p_position + get_position(p_abort),p_abort);
31  break;
32  case seek_from_eof:
33  seek(p_position + get_size_ex(p_abort),p_abort);
34  break;
35  default:
36  throw exception_io_data();
37  }
38 }
39 
40 static void makeBuffer(pfc::array_t<uint8_t> & buffer, size_t size) {
41  for(;;) {// Tolerant malloc - allocate a smaller buffer if we're unable to acquire the requested size.
42  try {
43  buffer.set_size_discard( size );
44  return;
45  } catch(std::bad_alloc) {
46  if (size < 256) throw;
47  size >>= 1;
48  }
49  }
50 }
51 
52 t_filesize file::g_transfer(stream_reader * p_src,stream_writer * p_dst,t_filesize p_bytes,abort_callback & p_abort) {
54  makeBuffer(temp, (t_size)pfc::min_t<t_filesize>(1024*1024*8,p_bytes));
55  void* ptr = temp.get_ptr();
56  t_filesize done = 0;
57  while(done<p_bytes) {
58  p_abort.check_e();
59  t_size delta = (t_size)pfc::min_t<t_filesize>(temp.get_size(),p_bytes-done);
60  delta = p_src->read(ptr,delta,p_abort);
61  if (delta<=0) break;
62  p_dst->write(ptr,delta,p_abort);
63  done += delta;
64  }
65  return done;
66 }
67 
68 void file::g_transfer_object(stream_reader * p_src,stream_writer * p_dst,t_filesize p_bytes,abort_callback & p_abort) {
69  if (g_transfer(p_src,p_dst,p_bytes,p_abort) != p_bytes)
70  throw exception_io_data_truncation();
71 }
72 
73 
74 void filesystem::g_get_canonical_path(const char * path,pfc::string_base & out)
75 {
76  TRACK_CALL_TEXT("filesystem::g_get_canonical_path");
77 
80  if (e.first(ptr)) do {
81  if (ptr->get_canonical_path(path,out)) return;
82  } while(e.next(ptr));
83  //no one wants to process this, let's copy over
84  out = path;
85 }
86 
87 void filesystem::g_get_display_path(const char * path,pfc::string_base & out)
88 {
89  TRACK_CALL_TEXT("filesystem::g_get_display_path");
91  if (!g_get_interface(ptr,path))
92  {
93  //no one wants to process this, let's copy over
94  out = path;
95  }
96  else
97  {
98  if (!ptr->get_display_path(path,out))
99  out = path;
100  }
101 }
102 
103 bool filesystem::g_get_native_path( const char * path, pfc::string_base & out) {
104  // Is proper file:// path?
105  if (foobar2000_io::extract_native_path( path, out ) ) return true;
106 
107  // Set anyway
108  out = path;
109 
110  // Maybe just a file:// less local path? Check for other protocol markers
111  // If no :// present, return true anyway
112  return strstr( path, "://" ) == NULL;
113 }
114 
115 filesystem::ptr filesystem::g_get_interface(const char * path) {
116  filesystem::ptr rv;
117  if (!g_get_interface(rv, path)) throw exception_io_no_handler_for_path();
118  return rv;
119 
120 }
121 bool filesystem::g_get_interface(service_ptr_t<filesystem> & p_out,const char * path)
122 {
125  if (e.first(ptr)) do {
126  if (ptr->is_our_path(path))
127  {
128  p_out = ptr;
129  return true;
130  }
131  } while(e.next(ptr));
132  return false;
133 }
134 
135 
136 void filesystem::g_open(service_ptr_t<file> & p_out,const char * path,t_open_mode mode,abort_callback & p_abort)
137 {
138  TRACK_CALL_TEXT("filesystem::g_open");
139  g_get_interface(path)->open(p_out,path,mode,p_abort);
140 }
141 
142 
143 void filesystem::g_open_timeout(service_ptr_t<file> & p_out,const char * p_path,t_open_mode p_mode,double p_timeout,abort_callback & p_abort) {
144  FB2K_RETRY_ON_SHARING_VIOLATION( g_open(p_out, p_path, p_mode, p_abort), p_abort, p_timeout);
145 }
146 
147 bool filesystem::g_exists(const char * p_path,abort_callback & p_abort)
148 {
149  t_filestats stats;
150  bool dummy;
151  try {
152  g_get_stats(p_path,stats,dummy,p_abort);
153  } catch(exception_io_not_found) {return false;}
154  return true;
155 }
156 
157 bool filesystem::g_exists_writeable(const char * p_path,abort_callback & p_abort)
158 {
159  t_filestats stats;
160  bool writeable;
161  try {
162  g_get_stats(p_path,stats,writeable,p_abort);
163  } catch(exception_io_not_found) {return false;}
164  return writeable;
165 }
166 
167 void filesystem::g_remove(const char * p_path,abort_callback & p_abort) {
168  g_get_interface(p_path)->remove(p_path,p_abort);
169 }
170 
171 void filesystem::g_remove_timeout(const char * p_path,double p_timeout,abort_callback & p_abort) {
172  FB2K_RETRY_FILE_MOVE( g_remove(p_path, p_abort), p_abort, p_timeout );
173 }
174 
175 void filesystem::g_move_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort) {
176  FB2K_RETRY_FILE_MOVE( g_move(p_src, p_dst, p_abort), p_abort, p_timeout );
177 }
178 
179 void filesystem::g_copy_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort) {
180  FB2K_RETRY_FILE_MOVE( g_copy(p_src, p_dst, p_abort), p_abort, p_timeout );
181 }
182 
183 void filesystem::g_create_directory(const char * p_path,abort_callback & p_abort)
184 {
185  g_get_interface(p_path)->create_directory(p_path,p_abort);
186 }
187 
188 void filesystem::g_move(const char * src,const char * dst,abort_callback & p_abort) {
191  if (e.first(ptr)) do {
192  if (ptr->is_our_path(src) && ptr->is_our_path(dst)) {
193  ptr->move(src,dst,p_abort);
194  return;
195  }
196  } while(e.next(ptr));
197  throw exception_io_no_handler_for_path();
198 }
199 
200 void filesystem::g_link(const char * p_src,const char * p_dst,abort_callback & p_abort) {
201  if (!foobar2000_io::_extract_native_path_ptr(p_src) || !foobar2000_io::_extract_native_path_ptr(p_dst)) throw exception_io_no_handler_for_path();
202  WIN32_IO_OP( CreateHardLink( pfc::stringcvt::string_os_from_utf8( p_dst ), pfc::stringcvt::string_os_from_utf8( p_src ), NULL) );
203 }
204 
205 void filesystem::g_link_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort) {
206  FB2K_RETRY_FILE_MOVE( g_link(p_src, p_dst, p_abort), p_abort, p_timeout );
207 }
208 
209 
210 void filesystem::g_list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort)
211 {
212  TRACK_CALL_TEXT("filesystem::g_list_directory");
213  g_get_interface(p_path)->list_directory(p_path,p_out,p_abort);
214 }
215 
216 
217 static void path_pack_string(pfc::string_base & out,const char * src)
218 {
219  out.add_char('|');
220  out << (unsigned) strlen(src);
221  out.add_char('|');
222  out << src;
223  out.add_char('|');
224 }
225 
226 static int path_unpack_string(pfc::string_base & out,const char * src)
227 {
228  int ptr=0;
229  if (src[ptr++]!='|') return -1;
230  int len = atoi(src+ptr);
231  if (len<=0) return -1;
232  while(src[ptr]!=0 && src[ptr]!='|') ptr++;
233  if (src[ptr]!='|') return -1;
234  ptr++;
235  int start = ptr;
236  while(ptr-start<len)
237  {
238  if (src[ptr]==0) return -1;
239  ptr++;
240  }
241  if (src[ptr]!='|') return -1;
242  out.set_string(&src[start],len);
243  ptr++;
244  return ptr;
245 }
246 
247 
248 void filesystem::g_open_precache(service_ptr_t<file> & p_out,const char * p_path,abort_callback & p_abort) {
249  service_ptr_t<filesystem> fs = g_get_interface(p_path);
250  if (fs->is_remote(p_path)) throw exception_io_object_is_remote();
251  fs->open(p_out,p_path,open_mode_read,p_abort);
252 }
253 
254 bool filesystem::g_is_remote(const char * p_path) {
255  return g_get_interface(p_path)->is_remote(p_path);
256 }
257 
258 bool filesystem::g_is_recognized_and_remote(const char * p_path) {
260  if (g_get_interface(fs,p_path)) return fs->is_remote(p_path);
261  else return false;
262 }
263 
264 bool filesystem::g_is_remote_or_unrecognized(const char * p_path) {
266  if (g_get_interface(fs,p_path)) return fs->is_remote(p_path);
267  else return true;
268 }
269 
270 bool filesystem::g_relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out)
271 {
272 
273  bool rv = false;
275 
276  if (g_get_interface(fs,file_path))
277  rv = fs->relative_path_create(file_path,playlist_path,out);
278 
279  return rv;
280 }
281 
282 bool filesystem::g_relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out)
283 {
286  if (e.first(ptr)) do {
287  if (ptr->relative_path_parse(relative_path,playlist_path,out)) return true;
288  } while(e.next(ptr));
289  return false;
290 }
291 
292 
293 
294 bool archive_impl::get_canonical_path(const char * path,pfc::string_base & out)
295 {
296  if (is_our_path(path))
297  {
298  pfc::string8 archive,file,archive_canonical;
299  if (g_parse_unpack_path(path,archive,file))
300  {
301  g_get_canonical_path(archive,archive_canonical);
302  make_unpack_path(out,archive_canonical,file);
303 
304  return true;
305  }
306  else return false;
307  }
308  else return false;
309 }
310 
311 bool archive_impl::is_our_path(const char * path)
312 {
313  if (!g_is_unpack_path(path)) return false;
314  const char * type = get_archive_type();
315  path += 9;
316  while(*type)
317  {
318  if (*type!=*path) return false;
319  type++;
320  path++;
321  }
322  if (*path!='|') return false;
323  return true;
324 }
325 
326 bool archive_impl::get_display_path(const char * path,pfc::string_base & out)
327 {
329  if (g_parse_unpack_path(path,archive,file))
330  {
331  g_get_display_path(archive,out);
332  out.add_string("|");
333  out.add_string(file);
334  return true;
335  }
336  else return false;
337 }
338 
339 void archive_impl::open(service_ptr_t<file> & p_out,const char * path,t_open_mode mode, abort_callback & p_abort)
340 {
341  if (mode != open_mode_read) throw exception_io_denied();
343  if (!g_parse_unpack_path(path,archive,file)) throw exception_io_not_found();
344  open_archive(p_out,archive,file,p_abort);
345 }
346 
347 
348 void archive_impl::remove(const char * path,abort_callback & p_abort) {
349  throw exception_io_denied();
350 }
351 
352 void archive_impl::move(const char * src,const char * dst,abort_callback & p_abort) {
353  throw exception_io_denied();
354 }
355 
356 bool archive_impl::is_remote(const char * src) {
358  if (g_parse_unpack_path(src,archive,file)) return g_is_remote(archive);
359  else throw exception_io_not_found();
360 }
361 
362 bool archive_impl::relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out) {
364  if (g_parse_unpack_path(file_path,archive,file))
365  {
366  pfc::string8 archive_rel;
367  if (g_relative_path_create(archive,playlist_path,archive_rel))
368  {
369  pfc::string8 out_path;
370  make_unpack_path(out_path,archive_rel,file);
371  out.set_string(out_path);
372  return true;
373  }
374  }
375  return false;
376 }
377 
378 bool archive_impl::relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out)
379 {
380  if (!is_our_path(relative_path)) return false;
381  pfc::string8 archive_rel,file;
382  if (g_parse_unpack_path(relative_path,archive_rel,file))
383  {
385  if (g_relative_path_parse(archive_rel,playlist_path,archive))
386  {
387  pfc::string8 out_path;
388  make_unpack_path(out_path,archive,file);
389  out.set_string(out_path);
390  return true;
391  }
392  }
393  return false;
394 }
395 
396 bool archive_impl::g_parse_unpack_path_ex(const char * path,pfc::string_base & archive,pfc::string_base & file, pfc::string_base & type) {
397  PFC_ASSERT( g_is_unpack_path(path) );
398  const char * base = path + unpack_prefix_len; // strstr(path, "//");
399  const char * split = strchr(path,'|');
400  if (base == NULL || split == NULL || base > split) return false;
401  // base += 2;
402  type.set_string( base, split - base );
403  int delta = path_unpack_string(archive,split);
404  if (delta<0) return false;
405  split += delta;
406  file = split;
407  return true;
408 }
409 bool archive_impl::g_parse_unpack_path(const char * path,pfc::string_base & archive,pfc::string_base & file) {
410  PFC_ASSERT( g_is_unpack_path(path) );
411  path = strchr(path,'|');
412  if (!path) return false;
413  int delta = path_unpack_string(archive,path);
414  if (delta<0) return false;
415  path += delta;
416  file = path;
417  return true;
418 }
419 
420 bool archive_impl::g_is_unpack_path(const char * path) {
421  return strncmp(path,unpack_prefix,unpack_prefix_len) == 0;
422 }
423 
424 void archive_impl::g_make_unpack_path(pfc::string_base & path,const char * archive,const char * file,const char * name)
425 {
426  path = unpack_prefix;
427  path += name;
428  path_pack_string(path,archive);
429  path += file;
430 }
431 
432 void archive_impl::make_unpack_path(pfc::string_base & path,const char * archive,const char * file) {g_make_unpack_path(path,archive,file,get_archive_type());}
433 
434 
435 FILE * filesystem::streamio_open(const char * path,const char * flags)
436 {
437  FILE * ret = 0;
438  pfc::string8 temp;
439  g_get_canonical_path(path,temp);
440  if (!strncmp(temp,"file://",7))
441  {
443  }
444  return ret;
445 }
446 
447 
448 namespace {
449 
450  class directory_callback_isempty : public directory_callback
451  {
452  bool m_isempty;
453  public:
454  directory_callback_isempty() : m_isempty(true) {}
455  bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats)
456  {
457  m_isempty = false;
458  return false;
459  }
460  bool isempty() {return m_isempty;}
461  };
462 
463  class directory_callback_dummy : public directory_callback
464  {
465  public:
466  bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) {return false;}
467  };
468 
469 }
470 
471 bool filesystem::g_is_empty_directory(const char * path,abort_callback & p_abort)
472 {
473  directory_callback_isempty callback;
474  try {
475  g_list_directory(path,callback,p_abort);
476  } catch(exception_io const &) {return false;}
477  return callback.isempty();
478 }
479 
480 bool filesystem::g_is_valid_directory(const char * path,abort_callback & p_abort) {
481  try {
482  directory_callback_dummy cb;
483  g_list_directory(path,cb,p_abort);
484  return true;
485  } catch(exception_io const &) {return false;}
486 }
487 
488 bool directory_callback_impl::on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) {
489  p_abort.check_e();
490  if (is_subdirectory) {
491  if (m_recur) {
492  try {
493  owner->list_directory(url,*this,p_abort);
494  } catch(exception_io const &) {}
495  }
496  } else {
497  m_data.add_item(pfc::rcnew_t<t_entry>(url,p_stats));
498  }
499  return true;
500 }
501 
502 namespace {
503  class directory_callback_impl_copy : public directory_callback
504  {
505  public:
506  directory_callback_impl_copy(const char * p_target, filesystem::ptr fs) : m_fs(fs)
507  {
508  m_target = p_target;
509  m_target.fix_dir_separator('\\');
510  }
511 
512  bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) {
513  const char * fn = url + pfc::scan_filename(url);
514  t_size truncat = m_target.length();
515  m_target += fn;
516  if (is_subdirectory) {
517  try {
518  m_fs->create_directory(m_target,p_abort);
519  } catch(exception_io_already_exists) {}
520  m_target += "\\";
521  owner->list_directory(url,*this,p_abort);
522  } else {
523  _copy(url, m_target, owner, p_abort);
524  }
525  m_target.truncate(truncat);
526  return true;
527  }
528  void _copy(const char * src, const char * dst, filesystem * srcFS, abort_callback & p_abort) {
529  service_ptr_t<file> r_src,r_dst;
530  t_filesize size;
531 
532  srcFS->open(r_src,src,filesystem::open_mode_read,p_abort);
533  size = r_src->get_size_ex(p_abort);
534  m_fs->open(r_dst,dst,filesystem::open_mode_write_new,p_abort);
535 
536  if (size > 0) {
537  try {
538  file::g_transfer_object(r_src,r_dst,size,p_abort);
539  } catch(...) {
540  r_dst.release();
541  abort_callback_dummy dummy;
542  try {m_fs->remove(dst,dummy);} catch(...) {}
543  throw;
544  }
545  }
546  }
547  private:
548  pfc::string8_fastalloc m_target;
549  filesystem::ptr m_fs;
550  };
551 }
552 
553 void filesystem::copy_directory(const char * src, const char * dst, abort_callback & p_abort) {
554  try {
555  this->create_directory( dst, p_abort );
556  } catch(exception_io_already_exists) {}
557  directory_callback_impl_copy cb(dst, this);
558  list_directory(src, cb, p_abort);
559 }
560 
561 void filesystem::g_copy_directory(const char * src,const char * dst,abort_callback & p_abort) {
562  filesystem::ptr dstFS = filesystem::g_get_interface(dst);
563  try {
564  dstFS->create_directory( dst, p_abort );
565  } catch(exception_io_already_exists) {}
566  directory_callback_impl_copy cb(dst, dstFS);
567  g_list_directory(src,cb,p_abort);
568 }
569 
570 void filesystem::g_copy(const char * src,const char * dst,abort_callback & p_abort) {
571  service_ptr_t<file> r_src,r_dst;
572  t_filesize size;
573 
574  g_open(r_src,src,open_mode_read,p_abort);
575  size = r_src->get_size_ex(p_abort);
576  g_open(r_dst,dst,open_mode_write_new,p_abort);
577 
578  if (size > 0) {
579  try {
580  file::g_transfer_object(r_src,r_dst,size,p_abort);
581  } catch(...) {
582  r_dst.release();
583  abort_callback_dummy dummy;
584  try {g_remove(dst,dummy);} catch(...) {}
585  throw;
586  }
587  }
588 }
589 
590 void stream_reader::read_object(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
591  if (read(p_buffer,p_bytes,p_abort) != p_bytes) throw exception_io_data_truncation();
592 }
593 
594 t_filestats file::get_stats(abort_callback & p_abort)
595 {
596  t_filestats temp;
597  temp.m_size = get_size(p_abort);
598  temp.m_timestamp = get_timestamp(p_abort);
599  return temp;
600 }
601 
602 t_filesize stream_reader::skip(t_filesize p_bytes,abort_callback & p_abort)
603 {
604  t_uint8 temp[256];
605  t_filesize todo = p_bytes, done = 0;
606  while(todo > 0) {
607  t_size delta,deltadone;
608  delta = sizeof(temp);
609  if (delta > todo) delta = (t_size) todo;
610  deltadone = read(temp,delta,p_abort);
611  done += deltadone;
612  todo -= deltadone;
613  if (deltadone < delta) break;
614  }
615  return done;
616 }
617 
618 void stream_reader::skip_object(t_filesize p_bytes,abort_callback & p_abort) {
619  if (skip(p_bytes,p_abort) != p_bytes) throw exception_io_data_truncation();
620 }
621 
622 void filesystem::g_open_write_new(service_ptr_t<file> & p_out,const char * p_path,abort_callback & p_abort) {
623  g_open(p_out,p_path,open_mode_write_new,p_abort);
624 }
625 void file::g_transfer_file(const service_ptr_t<file> & p_from,const service_ptr_t<file> & p_to,abort_callback & p_abort) {
626  t_filesize length = p_from->get_size(p_abort);
627  p_from->reopen( p_abort );
628 // p_from->seek(0,p_abort);
629  p_to->seek(0,p_abort);
630  p_to->set_eof(p_abort);
631  if (length == filesize_invalid) {
632  g_transfer(p_from, p_to, ~0, p_abort);
633  } else if (length > 0) {
634  g_transfer_object(p_from,p_to,length,p_abort);
635  }
636 }
637 
638 void filesystem::g_open_temp(service_ptr_t<file> & p_out,abort_callback & p_abort) {
639  g_open(p_out,"tempfile://",open_mode_write_new,p_abort);
640 }
641 
642 void filesystem::g_open_tempmem(service_ptr_t<file> & p_out,abort_callback & p_abort) {
643  g_open(p_out,"tempmem://",open_mode_write_new,p_abort);
644 }
645 
646 file::ptr filesystem::g_open_tempmem() {
647  abort_callback_dummy aborter;
648  file::ptr f; g_open_tempmem(f, aborter); return f;
649 }
650 
651 void archive_impl::list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort) {
652  throw exception_io_not_found();
653 }
654 
655 void archive_impl::create_directory(const char * path,abort_callback &) {
656  throw exception_io_denied();
657 }
658 
659 void filesystem::g_get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) {
660  TRACK_CALL_TEXT("filesystem::g_get_stats");
661  return g_get_interface(p_path)->get_stats(p_path,p_stats,p_is_writeable,p_abort);
662 }
663 
664 void archive_impl::get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) {
666  if (g_parse_unpack_path(p_path,archive,file)) {
667  if (g_is_remote(archive)) throw exception_io_object_is_remote();
668  p_is_writeable = false;
669  p_stats = get_stats_in_archive(archive,file,p_abort);
670  }
671  else throw exception_io_not_found();
672 }
673 
674 
675 bool file::is_eof(abort_callback & p_abort) {
676  t_filesize position,size;
677  position = get_position(p_abort);
678  size = get_size(p_abort);
679  if (size == filesize_invalid) return false;
680  return position >= size;
681 }
682 
683 
685 {
686  return pfc::fileTimeNow();
687 }
688 
689 void stream_reader::read_string_ex(pfc::string_base & p_out,t_size p_bytes,abort_callback & p_abort) {
690  const t_size expBase = 64*1024;
691  if (p_bytes > expBase) {
692  pfc::array_t<char> temp;
693  t_size allocWalk = expBase;
694  t_size done = 0;
695  for(;;) {
696  const t_size target = pfc::min_t(allocWalk, p_bytes);
697  temp.set_size(target);
698  read_object(temp.get_ptr() + done, target - done, p_abort);
699  if (target == p_bytes) break;
700  done = target;
701  allocWalk <<= 1;
702  }
703  p_out.set_string(temp.get_ptr(), p_bytes);
704  } else {
705  pfc::string_buffer buf(p_out, p_bytes);
706  read_object(buf.get_ptr(),p_bytes,p_abort);
707  }
708 }
709 void stream_reader::read_string(pfc::string_base & p_out,abort_callback & p_abort)
710 {
711  t_uint32 length;
712  read_lendian_t(length,p_abort);
713  read_string_ex(p_out,length,p_abort);
714 }
715 
716 void stream_reader::read_string_raw(pfc::string_base & p_out,abort_callback & p_abort) {
717  enum {delta = 256};
718  char buffer[delta];
719  p_out.reset();
720  for(;;) {
721  t_size delta_done;
722  delta_done = read(buffer,delta,p_abort);
723  p_out.add_string(buffer,delta_done);
724  if (delta_done < delta) break;
725  }
726 }
727 void stream_writer::write_string(const char * p_string,t_size p_len,abort_callback & p_abort) {
728  t_uint32 len = pfc::downcast_guarded<t_uint32>(pfc::strlen_max(p_string,p_len));
729  write_lendian_t(len,p_abort);
730  write_object(p_string,len,p_abort);
731 }
732 
733 void stream_writer::write_string(const char * p_string,abort_callback & p_abort) {
734  write_string(p_string,~0,p_abort);
735 }
736 
737 void stream_writer::write_string_raw(const char * p_string,abort_callback & p_abort) {
738  write_object(p_string,strlen(p_string),p_abort);
739 }
740 
741 void file::truncate(t_uint64 p_position,abort_callback & p_abort) {
742  if (p_position < get_size(p_abort)) resize(p_position,p_abort);
743 }
744 
745 
746 #ifdef _WIN32
747 namespace {
748  //rare/weird win32 errors that didn't make it to the main API
749  PFC_DECLARE_EXCEPTION(exception_io_device_not_ready, exception_io,"Device not ready");
750  PFC_DECLARE_EXCEPTION(exception_io_invalid_drive, exception_io_not_found,"Drive not found");
751  PFC_DECLARE_EXCEPTION(exception_io_win32, exception_io,"Generic win32 I/O error");
752  PFC_DECLARE_EXCEPTION(exception_io_buffer_overflow, exception_io,"The file name is too long");
753  PFC_DECLARE_EXCEPTION(exception_io_invalid_path_syntax, exception_io,"Invalid path syntax");
754 
755  class exception_io_win32_ex : public exception_io_win32 {
756  public:
757  exception_io_win32_ex(DWORD p_code) : m_msg(pfc::string_formatter() << "I/O error (win32 #" << (t_uint32)p_code << ")") {}
758  exception_io_win32_ex(const exception_io_win32_ex & p_other) {*this = p_other;}
759  const char * what() const throw() {return m_msg;}
760  private:
761  pfc::string8 m_msg;
762  };
763 }
764 
765 PFC_NORETURN void foobar2000_io::win32_file_write_failure(DWORD p_code, const char * path) {
766  if (p_code == ERROR_ACCESS_DENIED) {
767  const DWORD attr = uGetFileAttributes(path);
768  if (attr != ~0 && (attr & FILE_ATTRIBUTE_READONLY) != 0) throw exception_io_denied_readonly();
769  }
770  exception_io_from_win32(p_code);
771 }
772 
773 PFC_NORETURN void foobar2000_io::exception_io_from_win32(DWORD p_code) {
774  //pfc::string_fixed_t<32> debugMsg; debugMsg << "Win32 I/O error #" << (t_uint32)p_code;
775  //TRACK_CALL_TEXT(debugMsg);
776  switch(p_code) {
777  case ERROR_ALREADY_EXISTS:
778  case ERROR_FILE_EXISTS:
779  throw exception_io_already_exists();
780  case ERROR_NETWORK_ACCESS_DENIED:
781  case ERROR_ACCESS_DENIED:
782  throw exception_io_denied();
783  case ERROR_WRITE_PROTECT:
784  throw exception_io_write_protected();
785  case ERROR_BUSY:
786  case ERROR_PATH_BUSY:
787  case ERROR_SHARING_VIOLATION:
788  case ERROR_LOCK_VIOLATION:
789  throw exception_io_sharing_violation();
790  case ERROR_HANDLE_DISK_FULL:
791  case ERROR_DISK_FULL:
792  throw exception_io_device_full();
793  case ERROR_FILE_NOT_FOUND:
794  case ERROR_PATH_NOT_FOUND:
795  throw exception_io_not_found();
796  case ERROR_BROKEN_PIPE:
797  case ERROR_NO_DATA:
798  throw exception_io_no_data();
799  case ERROR_NETWORK_UNREACHABLE:
800  case ERROR_NETNAME_DELETED:
801  throw exception_io_network_not_reachable();
802  case ERROR_NOT_READY:
803  throw exception_io_device_not_ready();
804  case ERROR_INVALID_DRIVE:
805  throw exception_io_invalid_drive();
806  case ERROR_CRC:
807  case ERROR_FILE_CORRUPT:
808  case ERROR_DISK_CORRUPT:
809  throw exception_io_file_corrupted();
810  case ERROR_BUFFER_OVERFLOW:
811  throw exception_io_buffer_overflow();
812  case ERROR_DISK_CHANGE:
813  throw exception_io_disk_change();
814  case ERROR_DIR_NOT_EMPTY:
815  throw exception_io_directory_not_empty();
816  case ERROR_INVALID_NAME:
817  throw exception_io_invalid_path_syntax();
818  case ERROR_NO_SYSTEM_RESOURCES:
819  case ERROR_NONPAGED_SYSTEM_RESOURCES:
820  case ERROR_PAGED_SYSTEM_RESOURCES:
821  case ERROR_WORKING_SET_QUOTA:
822  case ERROR_PAGEFILE_QUOTA:
823  case ERROR_COMMITMENT_LIMIT:
824  throw exception_io("Insufficient system resources");
825  case ERROR_IO_DEVICE:
826  throw exception_io("Device error");
827  default:
828  throw exception_io_win32_ex(p_code);
829  }
830 }
831 #endif
832 
833 t_filesize file::get_size_ex(abort_callback & p_abort) {
834  t_filesize temp = get_size(p_abort);
835  if (temp == filesize_invalid) throw exception_io_no_length();
836  return temp;
837 }
838 
839 void file::ensure_local() {
840  if (is_remote()) throw exception_io_object_is_remote();
841 }
842 
843 void file::ensure_seekable() {
844  if (!can_seek()) throw exception_io_object_not_seekable();
845 }
846 
847 bool filesystem::g_is_recognized_path(const char * p_path) {
848  filesystem::ptr obj;
849  return g_get_interface(obj,p_path);
850 }
851 
852 t_filesize file::get_remaining(abort_callback & p_abort) {
853  t_filesize length = get_size_ex(p_abort);
854  t_filesize position = get_position(p_abort);
855  pfc::dynamic_assert(position <= length);
856  return length - position;
857 }
858 
859 void file::probe_remaining(t_filesize bytes, abort_callback & p_abort) {
860  t_filesize length = get_size(p_abort);
861  if (length != ~0) {
862  t_filesize remaining = length - get_position(p_abort);
863  if (remaining < bytes) throw exception_io_data_truncation();
864  }
865 }
866 
867 
868 t_filesize file::g_transfer(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort) {
869  return g_transfer(pfc::implicit_cast<stream_reader*>(p_src.get_ptr()),pfc::implicit_cast<stream_writer*>(p_dst.get_ptr()),p_bytes,p_abort);
870 }
871 
872 void file::g_transfer_object(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort) {
873  if (p_bytes > 1024) /* don't bother on small objects */
874  {
875  t_filesize srcFileSize = p_src->get_size(p_abort); // detect truncation
876  if (srcFileSize != ~0) {
877  t_filesize remaining = srcFileSize - p_src->get_position(p_abort);
878  if (p_bytes > remaining) throw exception_io_data_truncation();
879  }
880 
881  t_filesize oldsize = p_dst->get_size(p_abort); // pre-resize the target file
882  if (oldsize != filesize_invalid) {
883  t_filesize newpos = p_dst->get_position(p_abort) + p_bytes;
884  if (newpos > oldsize) p_dst->resize(newpos ,p_abort);
885  }
886 
887  }
888  g_transfer_object(pfc::implicit_cast<stream_reader*>(p_src.get_ptr()),pfc::implicit_cast<stream_writer*>(p_dst.get_ptr()),p_bytes,p_abort);
889 }
890 
891 
892 void foobar2000_io::generate_temp_location_for_file(pfc::string_base & p_out, const char * p_origpath,const char * p_extension,const char * p_magic) {
893  hasher_md5_result hash;
894  {
896  hasher_md5_state state;
897  hasher->initialize(state);
898  hasher->process(state,p_origpath,strlen(p_origpath));
899  hasher->process(state,p_extension,strlen(p_extension));
900  hasher->process(state,p_magic,strlen(p_magic));
901  hash = hasher->get_result(state);
902  }
903 
904  p_out = p_origpath;
905  p_out.truncate(p_out.scan_filename());
906  p_out += "temp-";
907  p_out += pfc::format_hexdump(hash.m_data,sizeof(hash.m_data),"");
908  p_out += ".";
909  p_out += p_extension;
910 }
911 
912 t_filesize file::skip_seek(t_filesize p_bytes,abort_callback & p_abort) {
913  const t_filesize size = get_size(p_abort);
914  if (size != filesize_invalid) {
915  const t_filesize position = get_position(p_abort);
916  const t_filesize toskip = pfc::min_t( p_bytes, size - position );
917  seek(position + toskip,p_abort);
918  return toskip;
919  } else {
920  this->seek_ex( p_bytes, seek_from_current, p_abort );
921  return p_bytes;
922  }
923 }
924 
925 t_filesize file::skip(t_filesize p_bytes,abort_callback & p_abort) {
926  if (p_bytes > 1024 && can_seek()) {
927  const t_filesize size = get_size(p_abort);
928  if (size != filesize_invalid) {
929  const t_filesize position = get_position(p_abort);
930  const t_filesize toskip = pfc::min_t( p_bytes, size - position );
931  seek(position + toskip,p_abort);
932  return toskip;
933  }
934  }
935  return stream_reader::skip(p_bytes,p_abort);
936 }
937 
938 bool foobar2000_io::is_native_filesystem( const char * p_fspath ) {
939  return _extract_native_path_ptr( p_fspath );
940 }
941 
942 bool foobar2000_io::_extract_native_path_ptr(const char * & p_fspath) {
943  static const char header[] = "file://"; static const t_size headerLen = 7;
944  if (strncmp(p_fspath,header,headerLen) != 0) return false;
945  p_fspath += headerLen;
946  return true;
947 }
948 bool foobar2000_io::extract_native_path(const char * p_fspath,pfc::string_base & p_native) {
949  if (!_extract_native_path_ptr(p_fspath)) return false;
950  p_native = p_fspath;
951  return true;
952 }
953 
954 bool foobar2000_io::extract_native_path_ex(const char * p_fspath, pfc::string_base & p_native) {
955  if (!_extract_native_path_ptr(p_fspath)) return false;
956  if (p_fspath[0] != '\\' || p_fspath[1] != '\\') {
957  p_native = "\\\\?\\";
958  p_native += p_fspath;
959  } else {
960  p_native = p_fspath;
961  }
962  return true;
963 }
964 
965 pfc::string stream_reader::read_string(abort_callback & p_abort) {
966  t_uint32 len;
967  read_lendian_t(len,p_abort);
968  return read_string_ex(len,p_abort);
969 }
970 pfc::string stream_reader::read_string_ex(t_size p_len,abort_callback & p_abort) {
971  pfc::rcptr_t<pfc::string8> temp; temp.new_t();
972  read_object(temp->lock_buffer(p_len),p_len,p_abort);
973  temp->unlock_buffer();
974  return pfc::string::t_data(temp);
975 }
976 
977 
978 void filesystem::remove_directory_content(const char * path, abort_callback & abort) {
979  class myCallback : public directory_callback {
980  public:
981  bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats) {
982  if (p_is_subdirectory) p_owner->list_directory(p_url, *this, p_abort);
983  try {
984  p_owner->remove(p_url, p_abort);
985  } catch(exception_io_not_found) {}
986  return true;
987  }
988  };
989  myCallback cb;
990  list_directory(path, cb, abort);
991 }
992 void filesystem::remove_object_recur(const char * path, abort_callback & abort) {
993  try {
994  remove_directory_content(path, abort);
995  } catch(exception_io_not_found) {}
996  remove(path, abort);
997 }
998 
999 void filesystem::g_remove_object_recur_timeout(const char * path, double timeout, abort_callback & abort) {
1000  FB2K_RETRY_FILE_MOVE( g_remove_object_recur(path, abort), abort, timeout );
1001 }
1002 
1003 void filesystem::g_remove_object_recur(const char * path, abort_callback & abort) {
1004  g_get_interface(path)->remove_object_recur(path, abort);
1005 }
1006 
1007 void foobar2000_io::purgeOldFiles(const char * directory, t_filetimestamp period, abort_callback & abort) {
1008 
1009  class myCallback : public directory_callback {
1010  public:
1011  myCallback(t_filetimestamp period) : m_base(filetimestamp_from_system_timer() - period) {}
1012  bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats) {
1013  if (!p_is_subdirectory && p_stats.m_timestamp < m_base) {
1014  try {
1015  filesystem::g_remove_timeout(p_url, 1, p_abort);
1016  } catch(exception_io_not_found) {}
1017  }
1018  return true;
1019  }
1020  private:
1021  const t_filetimestamp m_base;
1022  };
1023 
1024  myCallback cb(period);
1025  filesystem::g_list_directory(directory, cb, abort);
1026 }
1027 
1028 void stream_reader::read_string_nullterm( pfc::string_base & out, abort_callback & abort ) {
1029  enum { bufCount = 256 };
1030  char buffer[bufCount];
1031  out.reset();
1032  size_t w = 0;
1033  for(;;) {
1034  char & c = buffer[w];
1035  this->read_object( &c, 1, abort );
1036  if (c == 0) {
1037  out.add_string( buffer, w ); break;
1038  }
1039  if (++w == bufCount ) {
1040  out.add_string( buffer, bufCount ); w = 0;
1041  }
1042  }
1043 }
1044 
1045 t_filesize stream_reader::skip_till_eof(abort_callback & abort) {
1046  t_filesize atOnce = 1024 * 1024;
1047  t_filesize done = 0;
1048  for (;; ) {
1049  abort.check();
1050  t_filesize did = this->skip(atOnce, abort);
1051  done += did;
1052  if (did != atOnce) break;
1053  }
1054  return done;
1055 }
1056 
1057 
1058 
1059 bool foobar2000_io::matchContentType(const char * fullString, const char * ourType) {
1060  t_size lim = pfc::string_find_first(fullString, ';');
1061  if (lim != ~0) {
1062  while(lim > 0 && fullString[lim-1] == ' ') --lim;
1063  }
1064  return pfc::stricmp_ascii_ex(fullString,lim, ourType, ~0) == 0;
1065 }
1066 bool foobar2000_io::matchProtocol(const char * fullString, const char * protocolName) {
1067  const t_size len = strlen(protocolName);
1068  if (pfc::stricmp_ascii_ex(fullString, len, protocolName, len) != 0) return false;
1069  return fullString[len] == ':' && fullString[len+1] == '/' && fullString[len+2] == '/';
1070 }
1071 void foobar2000_io::substituteProtocol(pfc::string_base & out, const char * fullString, const char * protocolName) {
1072  const char * base = strstr(fullString, "://");
1073  if (base) {
1074  out = protocolName; out << base;
1075  } else {
1076  PFC_ASSERT(!"Should not get here");
1077  out = fullString;
1078  }
1079 }
virtual void remove(const char *p_path, abort_callback &p_abort)=0
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)
bool matchContentType(const char *fullString, const char *ourType)
t_seek_mode
Seeking mode constants. Note: these are purposedly defined to same values as standard C SEEK_* consta...
Definition: filesystem.h:207
static void g_transfer_object(stream_reader *src, stream_writer *dst, t_filesize bytes, abort_callback &p_abort)
Helper; transfers specified number of bytes between streams. Throws exception if requested number of ...
t_int64 t_sfilesize
Type used for file size related variables when a signed value is needed.
Definition: filesystem.h:10
const t_item * get_ptr() const
Definition: array.h:213
uint8_t t_uint8
Definition: int_types.h:9
t_filesize m_size
Size of the file.
Definition: filesystem.h:66
void new_t()
Definition: rcptr.h:101
t_filetimestamp filetimestamp_from_system_timer()
Definition: filesystem.cpp:684
A class providing abstraction for an open file object, with reading/writing/seeking methods...
Definition: filesystem.h:203
PFC_NORETURN void win32_file_write_failure(DWORD p_code, const char *path)
Definition: filesystem.cpp:765
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)
DWORD SHARED_EXPORT uGetFileAttributes(const char *fn)
virtual void add_string(const char *p_string, t_size p_length=~0)=0
uint64_t t_uint64
Definition: int_types.h:3
char m_data[16]
Definition: hasher_md5.h:6
T * get_ptr() const
Definition: service.h:117
PFC_NORETURN void exception_io_from_win32(DWORD p_code)
Definition: filesystem.cpp:773
abort_callback_impl abort_callback_dummy
bool first(service_ptr_t< t_query > &p_out)
Definition: service.h:581
bool extract_native_path_ex(const char *p_fspath, pfc::string_base &p_native)
Definition: filesystem.cpp:954
bool _extract_native_path_ptr(const char *&p_fspath)
Definition: filesystem.cpp:942
void unlock_buffer()
Definition: string_base.h:416
static int path_unpack_string(pfc::string_base &out, const char *src)
Definition: filesystem.cpp:226
rcptr_t< string_base const > t_data
Definition: stringNew.h:21
static const t_filesize filesize_invalid
Invalid/unknown file size constant. Also see: t_filesize.
Definition: filesystem.h:16
int stricmp_ascii_ex(const char *const s1, t_size const len1, const char *const s2, t_size const len2)
void check() const
Checks if user has requested the operation to be aborted, and throws exception_aborted if so...
bool is_native_filesystem(const char *p_fspath)
Definition: filesystem.cpp:938
t_size scan_filename() const
Definition: string_base.h:234
t_open_mode
Enumeration specifying how to open a file. See: filesystem::open(), filesystem::g_open().
Definition: filesystem.h:382
void dynamic_assert(bool p_condition, const char *p_msg)
Definition: primitives.h:239
Interface for archive reader services. When implementing, derive from archive_impl rather than from d...
Definition: filesystem.h:514
t_size strlen_max(const char *ptr, t_size max)
Definition: string_base.h:91
size_t t_size
Definition: int_types.h:48
string8_fastalloc string_formatter
Definition: string_base.h:614
static const unsigned unpack_prefix_len
Definition: filesystem.cpp:4
virtual bool on_entry(filesystem *p_owner, abort_callback &p_abort, const char *p_url, bool p_is_subdirectory, const t_filestats &p_stats)=0
void set_size(t_size p_size)
Definition: array.h:104
bool matchProtocol(const char *fullString, const char *protocolName)
void release()
Definition: service.h:109
Dummy abort_callback that never gets aborted. Slightly more efficient than the regular one especiall...
t_size scan_filename(const char *ptr)
t_filetimestamp m_timestamp
Time of last file modification.
Definition: filesystem.h:68
virtual void set_string(const char *p_string, t_size p_length=~0)
Definition: string_base.h:197
static void makeBuffer(pfc::array_t< uint8_t > &buffer, size_t size)
Definition: filesystem.cpp:40
bool extract_native_path(const char *p_fspath, pfc::string_base &p_native)
Definition: filesystem.cpp:948
void purgeOldFiles(const char *directory, t_filetimestamp period, abort_callback &abort)
void generate_temp_location_for_file(pfc::string_base &p_out, const char *p_origpath, const char *p_extension, const char *p_magic)
Definition: filesystem.cpp:892
PFC_DECLARE_EXCEPTION(exception_bad_cuesheet, exception_io_data,"Invalid cuesheet")
void substituteProtocol(pfc::string_base &out, const char *fullString, const char *protocolName)
Entrypoint service for all filesystem operations. Implementation: standard implementations for local ...
Definition: filesystem.h:378
static void g_open(service_ptr_t< file > &p_out, const service_ptr_t< file > &p_source, abort_callback &p_abort)
Static helper querying existing unpacker implementations until one that successfully opens specified ...
Definition: filesystem.cpp:6
char * lock_buffer(t_size n)
Definition: string_base.h:408
void add_char(t_uint32 c)
Definition: string_base.cpp:5
t_uint64 t_filetimestamp
Type used for file timestamp related variables. 64-bit value representing the number of 100-nanosecon...
Definition: filesystem.h:12
virtual void list_directory(const char *p_path, directory_callback &p_out, abort_callback &p_abort)=0
uint64_t fileTimeNow()
Definition: timers.cpp:65
t_uint64 t_filesize
Type used for file size related variables.
Definition: filesystem.h:8
void check_e() const
For compatibility with old code. Do not call.
New EXPERIMENTAL string class, allowing efficient copies and returning from functions. Does not implement the string_base interface so you still need string8 in many cases. Safe to pass between DLLs, but since a reference is used, objects possibly created by other DLLs must be released before owning DLLs are unloaded.
Definition: stringNew.h:19
void set_size_discard(t_size p_size)
Definition: array.h:128
Stores file stats (size and timestamp).
Definition: filesystem.h:64
t_size string_find_first(const char *p_string, char p_tofind, t_size p_start)
static const char unpack_prefix[]
Definition: filesystem.cpp:3
static void path_pack_string(pfc::string_base &out, const char *src)
Definition: filesystem.cpp:217
static void g_remove_timeout(const char *p_path, double p_timeout, abort_callback &p_abort)
Attempts to remove file at specified path; if the operation fails with a sharing violation error...
Definition: filesystem.cpp:171
virtual void truncate(t_size len)=0
t_size get_size() const
Definition: array.h:130
bool next(service_ptr_t< t_query > &p_out)
Definition: service.h:587
string8_t< pfc::alloc_fast_aggressive > string8_fastalloc
Definition: string_base.h:435
uint32_t t_uint32
Definition: int_types.h:5