====== Biblioteka boost::filesystem ====== **Michał Wasiak, //G1ISI//** ===== Wstęp ===== Biblioteka boost::filesystem wprowadza przenośne udogodnienia w zarządzaniu ścieżkami dostępu do katalogów i plików. ===== Cechy ===== * Programy używające tej biblioteki charakteryzuje pełna przenośność. Składnia, jak i zachowanie kodu programu są całkowicie niezależne od systemu operacyjnego. * Użycie biblioteki uważa się za bezpieczne, ponieważ ewentualne błędy nie mogą zostać zignorowane, gdyż większość funkcji zwraca wyjątki przy napotkaniu błędów. ===== Pliki nagłówkowe ===== * Nagłówek '''' wprowadza klasę //basic_path// i jej pochodne: //path// i //wpath//. Poza tym udostępnia wiele pomocnych operacji na plikach i katalogach. Klasa //basic_directory_iterator// umożliwia niezwykle użytczne iteracje po zawartości katalogów. * Nagłowek '''' wprowadza takie same elementy co nagłówek //fstream// standardowej biblioteki C++, lecz same pliki są identyfikowane przez obiekty //basic_path//, a nie //char *s//. ===== Podstawowe klasy ===== ==== basic_path ==== Obiekt klasy //path// może być stworzony w następujący sposób: path my_path("sciezka/plik.txt"); String podawany jak argument może być ścieżką w ogólnym przenośnym formacie lub w natywnym formacie systemu operacyjnego. Klasa //path// posiada konstruktory konwersji z //const char*// i //std::string&//, więc pomimo tego, że biblioteka Filesystem używa parametru formalnego //path&// to i tak dozwolone jest podawanie jako parametrów napisów w stylu C: remove_all("plik"); create_directory("teczka"); if(!exists("katalog/obraz")) std::cout << "Błędna ścieżka\n"; Ścieżki mogą zawierać odwołanie do bieżącego katalogu używając notacji ".", a także do nadkatalogu, używając zapisu "..". ==== basic_directory_iterator ==== Klasa //basic_directory_iterator// jest ważnym elementem biblioteki Filesystem. Wprowadza iterator wejściowy po zawartości katalogu z wartością typu //basic_path//. Jako przykład użycia tego iteratora przedstawiona zostanie funkcja, która rekursywnie wyszukuje plik o podanej nazwie w podanym katalogu i jego podkatalogach. Zwraca wartość //bool// i ewentualnie ścieżkę do odnalezionego pliku: namespace bf = boost::filesystem; bool find_file( const bf::path& dir_path, const std::string& file_name, path& path_found ) { if(!exists(dir_path) || !is_directory(dir_path)) return false; directory_iterator iter(dir_path), end_iter; for(; iter != end_iterl ++iter) { if(bf::is_directory(*iter)) { if(find_file(*iter, file_name, path_found)) return true; } else if(iter->leaf() == file_name) { path_found = *iter; return true; } } return false; } Funkcja najpierw sprawdza czy podana ścieżka w //path_dir// istnieje i jest katalogiem. Później tworzy obiekt //directory_iterator// podając ściężkę do konstruktora. Pętla iteruje po wszystkich elementach katalogu i dla każdego napotkanego podkatalogu wywołuje rekursywnie funkcję szukającą. Każdy rozpoznany plik jest porównywany co do nazwy z paramatrem //file_name// i jeśli znaleziony zostanie plik o podanej nazwie to do //path_found// kopiowana jest scieżka do tego pliku i zwracana jest wartość 'true'. Jeśli nic nie zostanie znalezione to zwrcana jest wartość 'false'. ===== Formaty ścieżek ===== Są dwa formaty łańcuchów opisujących ścieżkę dostępu: * Przenośny format opisany przez POSIX; * Natywny format ścieżki dostępu definiowany przez system operacyjny. Przykłady:\\ //OpenVMS:// ''"SYS1::DISK1:[DANE.FILMY]"''\\ //Windows:// ''"c:\dane\filmy"'' ===== Pomocne funkcje ===== void copy_file(const Path1& from_fp, const Path2& to_fp); Funkcja kopiuje zawartość pliku //from_fp// do pliku //to_fp//. void rename(const Path1& from_p, const Path2& to_p); Funkcja zmienia nazwę pliku //from_fp// na nową //to_p//. bool remove(const Path& p); Funkcja usuwa plik //p//, jeśli plik taki istnieje. Zwraca wartość wyrażenia ''exists(p)''. bool create_directory(const Path& dp); Funkcja tworzy katalog //dp//. Zwraca wartość //true//, jeśli katalog został utworzony, w przeciwnym razie zwraca wartość //false//. uintmax_t file_size(const Path& p); Funkcja zwraca rozmiar pliku //p// w bajtach. std::time_t last_write_time(const Path& p); Funkcja zwraca czas ostatniej modyfikacji elementu //p//. void last_write_time(const Path& p, const std::time_t new_time); Funkcja ustawia czas ostatniej modyfikacji pliku //p// na //new_time//. template typename Path::string_type extension(const Path & p); Funkcja zwracająca rozszerzenie pliku. Jeśli ''p.leaf()'' zawiera kropkę, to funkcja zwraca podciąg z ''p.leaf()'' od ostatniej kropki aż do ostatniego znaku nazwy. W przeciwnym razie zwraca pusty łańcuch. template typename Path::string_type basename(const Path & p); Funkcja zwracająca nazwę pliku bez rozszerzenia. Jeśli ''p.leaf()'' zawiera kropkę, to funkcja zwraca podciąg z ''p.leaf()'' od początku nazwy do ostatniej kropki (kropka nie jest zawarta). W przeciwnym wypadku zwraca ''p.leaf()''. template Path replace_extension(const Path & p, const typename Path::string_type & new_extension); Funkcja zmienia rozszerzenie pliku //p// na nowe //new_extension//. ===== Przykład użycia ===== Prosty program wypisujący zawartość katalogu: namespace bf = boost::filesystem; void listing(const bf::path& p) { unsigned long dir_count = 0; unsigned long file_count = 0; unsigned long other_count = 0; unsigned long err_count = 0; if(!bf::exists(p)) { std::cout << "Nieprawidlowa sciezka: " << p.native_file_string() << std::endl; return; } if(bf::is_directory(p)) { std::cout << "W katalogu: " << p.native_file_string() << "\n\n"; bf::directory_iterator iter(p), end_iter; for(; iter != end_iter; ++iter) { try { if(bf::is_directory(*iter)) { ++dir_count; std::cout << iter->leaf() << " [katalog]\n"; } else if(fs::is_regular(iter->status())) { ++file_count; std::cout << iter->leaf() << " (" << bf::file_size(*iter) <<" B)\n"; } else { ++other_count; std::cout << iter->leaf() << " [inny]\n"; } } catch(const std::exception& ex) { ++err_count; std::cout << iter->leaf() << ": " << ex.what() << std::endl; } } std::cout << std::endl << file_count << " plikow\n" << dir_count << " katalogow\n" << other << " innych\n" << err_count << " błedów\n"; } else { // musi byc plikiem std::cout << "Plik: " << p.native_file_string() << std::endl; } } ===== Uwagi ===== Przy całym dobrodziejstwie niesionym przez bibliotekę Filesystem trzeba także pamiętać o pewnych pułapkach czyhających na programistę. Otóż podczas wykonywania funkcji może wystąpić wyścig, a wtedy wartość zwracana przez funkcję może ulec zmianie do czasu powrotu do funkcji wywołującej. Pliki lub katalogi są często współdzielone i przez to ich stan może zostać zmieniony prze inny wątek, proces, a nawet inny komputer posiadający sieciowy dostęp do systemu plików. Oto kilka przykładów pokazujących istotę problemu: assert(exists("foo") == exists("foo")); W tym przypadku może się zdarzyć, iż nieistniejący plik bądź katalog zostanie utworzony lub istniejący zostanie usunięty pomiędzy pierwszy a drugim wywołaniem //exists()//. Taka sytuacja może nastąpić, jeśli podczas wykonania przykładowego kodu inny wątek, proces lub komputer także operuje w tym samym folderze. remove_all("foo"); assert(!exists("foo")); Taka próba nie powiedzie się, jeśli między wywołaniem //remove_all()// a wywołaniem //exists()// inny wątek, proces lub komputer stworzy plik bądź katalog o nazwie "foo". assert(is_directory("foo") == is_directory("foo")); Zawiedzie, w przypadku gdy inny wątek, proces badź też komputer pomiędzy dwoma wywołaniami //is_directory()// usunie istniejący plik "foo" i stworzy katalog o nazwie "foo".