Laboratorium 7: Zdarzenia, Pliki, System plików, Strumienie #
Kod startowy #
- NoteReader/
- NoteReader.Test/
- task.en.md
- task.pl.md
Celem tego laboratorium jest dokończenie implementacji aplikacji do czytania notatek. Na szczęście dostępny jest gotowy szkielet takiej właśnie aplikacji. Musisz jedynie zaimplementować części bezpośrednio współpracujące z systemem plików. Etapy muszą być wykonane po kolei.
Etap 1: Narzędzia do systemu plików #
Przygotuj następujące funkcje w FileSystemUtils.cs. Mają one przygotowane testy, dzięki którym możesz wstępnie zweryfikować poprawność rozwiązania przed oddaniem etapu.
1.1. PrepareDirectory – 2 pkt #
Nasza aplikacja do czytania notatek będzie otwierać katalog i go przeglądać. Funkcja string PrepareDirectory(string path) ma przygotować katalog do pracy z aplikacją. Jeśli więc path wskazuje na katalog, nie trzeba nic robić — funkcja zwraca ten sam ciąg.
Jeśli path wskazuje na plik z rozszerzeniem .zip, jest to archiwum i należy je rozpakować do tymczasowego środowiska przeglądania. Utwórz w katalogu tymczasowym folder o takiej samej nazwie jak archiwum, ale bez rozszerzenia. Rozpakuj zawartość archiwum zip do tego folderu i zwróć ścieżkę do tego katalogu.
Przykład: wywołanie z "library.zip" (o ile plik istnieje) powinno zwrócić /tmp/library/, C:\Users\...\AppData\Local\Temp\library\ lub inną ścieżkę wskazywaną przez system jako katalog tymczasowy.
W innym przypadku, funkcja powinna rzucić wyjątek FileSystemException z odpowiednim komunikatem.
1.2. CountFiles – 1 pkt #
Użytkownik może chcieć wiedzieć, ile notatek zgromadził. Funkcja int CountFiles(string path, string suffix) rekurencyjnie zlicza wszystkie pliki w katalogu path, których nazwy kończą się na suffix.
Etap 2: Obserwator katalogu #
Główną siłą naszej aplikacji do czytania notatek jest responsywność. Musi reagować na zmiany w przeglądanym katalogu. Gdy plik lub katalog zostanie dodany, usunięty lub zmieni się jego nazwa, program musi odpowiednio zareagować, aby interfejs odpowiadał temu, co rzeczywiście znajduje się w systemie plików. Ten etap także ma przygotowane testy.
2.1. Implementacja klasy DirectoryWatcher – 4 pkt
#
Możesz pobrać całą zawartość obserwowanego katalogu tylko raz — przy inicjalizacji. Później należy dodawać, usuwać i zmieniać nazwy katalogów oraz plików systematycznie w reakcji na zmiany. Klasa udostępnia:
- konstruktor z jednym argumentem
string, wskazującym katalog do obserwacji; - zdarzenie
EventHandler? DirectoryChanged, wywoływane za każdym razem, gdy plik lub katalog wewnątrz obserwowanego katalogu zostanie utworzony, usunięty lub zmieni nazwę; - właściwość tylko do odczytu
string[] Files, zwracającą nazwy plików bezpośrednio w obserwowanym katalogu w nowej tablicy; - właściwość tylko do odczytu
string[] Directories, zwracającą nazwy katalogów bezpośrednio w obserwowanym katalogu w nowej tablicy.
Klasa implementuje także interfejs IDisposable i powinna zwalniać wszystkie zasoby w funkcji Dispose.
Wskazówka: użyj FileSystemWatcher. Należy uważać - w niektórych systemach operacyjnych generuje on zdarzenia przed zakończeniem operacji. Oznacza to, że dla właśnie utworzonego katalogu src, Directory.Exists("path_to/src") może zwrócić false. Użyj NotifyFilter, aby obserwować wyłącznie pliki lub katalogi.
2.2. WatchDirectory – 1 pkt #
W FileSystemUtils.cs utwórz DirectoryWatcher dla katalogu path. Następnie zasubskrybuj DirectoryChanged funkcją, która wypisze katalogi w formacie “d:<nazwa_katalogu>”, a potem pliki w formacie “f:<nazwa_pliku>”. Umożliwia Ci to to śledzenie zmian i testowanie ich aż do momentu wciśnięcia enter. Zachowaj kod tworzący Log i dbający o jego zamknięcie. Możesz używać Log do debugowania. Upewnij się, że wszystkie zasoby będące IDisposable są prawidłowo zwalniane.
Etap 3: Note Reader #
Czas połączyć funkcjonalność z zewnętrznym API. Implementacja istniejącego interfejsu pozwoli nam dostarczyć funkcjonalność klasie NoteReader korzystającej z ConsolePainter, bez konieczności bezpośredniego kontaktu z którymkolwiek z nich. Twoim zadaniem jest implementacja dwóch źródeł danych. Po tym będziesz w stanie czytać notatki za pomocą interaktywnej aplikacji konsolowej. Aby uruchomić aplikację, odkomentuj #define w Program.cs oraz NoteReader.cs. Sterowanie: strzałki w górę i dół, enter oraz escape.
3.1 Źródła i przetwarzanie zdarzeń wejścia – 1 pkt #
Zaimplementuj poprawne interfejsy i właściwości dla DirectorySource oraz FileSource, tak aby NoteReader się kompilował. Nie muszą jeszcze działać poprawnie.
Na końcu konstruktora NoteReader zasubskrybuj inputEventGenerator.Input funkcją lambda, tak aby jego zdarzenia - naciskane klawisze - były przetwarzane przez HandleInput. Nie zmieniaj funkcji HandleInput.
3.2. DirectorySource – 2 pkt #
Umożliwia przeglądanie i wybieranie katalogów oraz plików w podanym katalogu. Implementuje interfejsy IDataSource<string> i IDisposable. Powinno być zawsze aktualne względem stanu systemu plików.
- Właściwość
Nameto ścieżka do obserwowanego katalogu. DatatoIEnumerable<string>zbudowane ze znaku wskazującego, czy to plik czy katalog i czy jest wybrany, oraz nazwy tego obiektu. Użyj znaczników podanych w komentarzu nad klasą. Wszystkie katalogi powinny mieć również liczbę plików z końcówkąpl.mdzawartych w nim i jego podkatalogach dopisaną po nazwie.- Przykład: wybrany katalog
Lab06, zawierającyindex.pl.md, będzie reprezentowany jako»Lab06 (1).
- Przykład: wybrany katalog
- Właściwość
Countzwraca łączną liczbę elementów. DataChangedto zdarzenie wywoływane przy każdej zmianie danych — zarówno zmianie plików lub katalogów jak i zmianie wyboru.Disposezwalnia wszystkie zasoby wymagające zwolnienia.
Klasa udostępnia także funkcjonalność zarządzania wyborem:
- właściwość
int Selectjest publicznie możliwa do odczytu, ale możliwa do ustawienia tylko prywatnie. Po zmianieData, właściwość ta powinna wskazywać na poprawny element, choć niekoniecznie ten sam. Jeśli nie ma dostępnych elementów, powinna być równa -1. Selectedzwraca nazwę wybranego pliku lub katalogu, bez znaczników.SelectUpiSelectDownodpowiednio zmniejszają i zwiększająSelecto jeden, z zabezpieczeniem zakresu wyboru do poprawnych wartości.
3.3. FileSource – 1 pkt #
To źródło reprezentuje plik tekstowy w systemie plików. Implementuje te same interfejsy co DirectorySource. Odczytuje linie z pliku i udostępnia je pod Data. Zawartość powinna zostać zastąpiona nową wtedy i tylko wtedy, gdy plik zostanie w jakikolwiek sposób zmodyfikowany (wykryte poprzez zmianę czasu ostatniego zapisu). Niektóre systemy operacyjne mogą wymagać busy waitingu ( 😥 ), aż Guard.FileReadAvailable potwierdzi, że plik jest dostępny do odczytu.
Przykładowe rozwiązanie #
- NoteReader/
- NoteReader.Test/