# Чтение, запись и удаление файлов

## Чтение файлов

Давайте предположим, что в нашей файловой системе есть файл `example.txt`, который содержит следующее:

```
- Привет! Ты из Китая, верно?
– Да, верно. Привет.
- Я слышал, что Китай был первой страной, которая приняла идеи коммунизма.
- Да, это правда. Мы пытаемся построить социалистическое общество с китайской спецификой.
- А что вы думаете об идеологии коммунизма?
- Я думаю, что коммунизм может быть хорошим идеалом, если будет правильно реализован. Он должен быть основан на равном распределении ресурсов и справедливости для всех людей.
- А что вы думаете о перестройке в России и распаде Советского Союза?
- Я думаю, что это был сложный период в истории России и СССР. Я уважаю решение народа России о переходе к рыночной экономике и демократии. Но я также считаю, что коммунизм может быть реализован лишь в том случае, если есть поддержка и понимание со стороны жителей страны.
```

Первое, что необходимо сделать для того, чтобы прочитать содержимое, это открыть файл. Открытие файла принимает 2 аргумента: путь до файла и права на этот файл

```ruby
# В данном случае мы открываем наш файл по пути '/some/path/example.txt' с правами на чтение
# результатом открытия файла будет обхект класса File содержащий значение файла, а также метаинформацию
file = File.open("/some/path/example.txt", "r")
```

Можно выделить 3 основных права на файл:

1. `r` - read/чтение
2. `a` - append/добавление (мы можем добавить в файл значение, но не можем изменить уже существующее)
3. `w` - write/запись

Кроме того, права указаны в порядке суммирования, что означает, что каждый пункт будет помимо своих собственных прав иметь еще и права на из пункта выше.

После того как мы открыли файл нам доступны следующие методы:

* `read` - перенос значения в строкове представление. При этом наши переносы строки, пробелы, табуляции сохраняются
* `readlines` - перенос значения в массив строк, где каждая новая строка будет новым значением в массиве
* `each_line` - возвращает объект `Enumerator`. Если открыть в нем блок кода, то можно проитерировать каждую строчку файла
* `readline` - читает строку в файле и при следующем вызове этого же метода будет прочитана следующая строка
* `seek` - перевод курсора к указанной позиции в файле. Принимает первым аргументом размер отступа, а вторым от чего именно будет отступ:
  * `:END` - с конца
  * `:SET` - с начала
  * `:CUR` - от текущей позиции
* `size` - размер файла в байтах
* `birthtime` - дата создания объекта
* `atime` - дата последнего доступа к файлу

Важно понимать отличие между `readlines` и `each_line` с `readline`. `reeadlines` читает содержимое файла целиком и целиком помещает его в массив. Это может быть эффективно при работе с небольшими файлами, но если файл весит 1 ГБ, то весь 1ГБ будет помещен в память компьютера, а как мы знаем размеры памяти куда более ограничены по сравнению с постоянной памятью по типу SSD или HHD. `each_line` и `readline` отличается своей возможностью `лениво` читать файл. Если текущая итерация обрабатвает 1 строчку файла, то остальные строчки не будут помещены в память. Когда итерация начнет обрабатывать 2 строчку, то данные из первой строки очистятся из памяти, а данные из 3 и последующей не будут в нее загружены.

После завершения работы нам необходимо всегда закрывать работу файла вызвав у него метод `close`. Кроме того, мы можем не вызывать этот метод, если будет работать с файлом внутри блока при открытии, т.к. закрытие файла будет вызвано автоматически по окончанию работы блока кода:

```ruby
# выглядит точно так же как и обычное открытие файла, только файл помещается не в объект, а в параметр блока кода
File.open("/some/path/example.txt", "r") do |file|
  # лениво итерируемся по каждой строчке файла, экономя память
  file.each_line do |line|
    puts line
  end
end
```

## Изменение файлов

Давайте сразу на примере посмотрим как мы можем записывать в файл:

```ruby
# Заметьте, для записи файла используется уже другие права на работу с файлом
# Если файла на момент работы с ним не существует, то он будет создан
File.open("/some/path/another_example.txt", "w") do |file|
  # Записываем в файл строку
  file.write("some data")
end
```

Обратите внимание, в примере выше запись в файл всегда будет перезаписывать уже существующие данные. Для того, чтобы нам вместо перезаписи дополнять данными пользуются флагом `a` при записи

```ruby
# Заметьте, для записи файла используется уже другие права на работу с файлом
# Если файла на момент работы с ним не существует, то он будет создан
File.open("/some/path/another_example.txt", "w") do |file|
  # Записываем в файл строку, старые данные остаются неизменными 
  file.write("some data", mode: "a")
end
```

Такой же результат мы получим, если откроем файл с правами `a`

```ruby
File.open("/some/path/another_example.txt", "a") do |file|
  # Записываем в файл строку, старые данные остаются неизменными 
  file.write("some data")
end
```

Помимо методов объекта существуют еще полезные методы класса, которые могут являться клонами метода объекта, либо дают новый функционал. Вот наиболее часто встречающиеся методы:

* `File.rename` - переименновать файл
* `File.size` - получить размер файла в байтах
* `File.exists?` - существует ли файл
* `File.directory?` - является ли файл директорией
* `File.file?` - является ли файл файлом
* `File.foreach` - такой же функционал как и у метода объекта `.each_line`
* `File.read` - такой же функционал как и у метода объекта `.read`
* `File.readlines` - такой же функционал как и у метода объекта `.readlines`

Также, методы класса самотоятельно открывают и закрывают файл, соответсвенно, нам об этом заботиться не нужно.

## Работа с директорией

С файлами мы разобрались, но что делать, когда мы хотим работать сразу с несколькими файлами, объеденных в одной директории ? Для этого у нас есть класс `Dir` со своимсобственным функционалом. Вот несколько наиболее полезных классовых методов у него:

* `Dir.mkdir` - создание новой директории. **Внимание** этот метод не создает вложнные каталоги. Если мы попытаемся создат ькаталог в несуществующем каталоге, то получим ошибку.
* `Dir.entries` - возвращает массив названий файлов и каталогов в указаной директории. Учтите6 что это именно массив названий файлов, а не объектов `File`.
* `Dir.glob` - Возвращает список файлов и директорий по паттерн матчингу. `*` обозначает любое количество символов, включая ноль символов. `**` обозначает любое количество символов, включая вложенные каталоги. Кроме символа \* также используются символы `?` (заменяет один любой символ) и `[]` (определяет диапазон возможных символов). Например:
  * `*.txt` - соответствует всем файлам с расширением .txt в текущей директории.
  * `docs/*.txt` - соответствует всем файлам с расширением .txt в папке docs, находящейся в текущей директории.
  * `**/*.txt` - соответствует всем файлам с расширением .txt в любой вложенной директории, начиная с текущей.

Давайте посмотрим на пример использования:

```ruby
# Получение списка файлов в текущем каталоге
# Внимание, file_name это только название файла без полного пути
Dir.entries(".") do |file_name|
  puts file_name
end
# Получение списка файлов в текущем каталоге и подкаталогах с расширением .rb
# Внимание, file_name это только название файла без полного пути
Dir.glob("**/*.rb") do |file_name|
  puts file_name
end
```

## Задание file\_tree

Напишите метод `file_hierachy(path)` принимающий путь до директории. Выведите в консоль файлы в древовидной форме. Как это должно выглядеть в консоли:

```ruby
file_hierachy('/home/ruby_course/Documents')

# /home/ruby_course/Documents
# /home/ruby_course/Documents/Projects
# /home/ruby_course/Documents/Projects/course
# /home/ruby_course/Documents/Projects/course/file1.txt
# /home/ruby_course/Documents/Projects/course/file2.txt
# /home/ruby_course/Documents/Projects/course/file3.txt
# /home/ruby_course/Documents/Projects/photos
# /home/ruby_course/Documents/Projects/photos/photo.jpg
# /home/ruby_course/Documents/Downloads/config.json
# /home/ruby_course/Documents/Downloads/archive.tar
# ...
```

## Задание folder\_size

Напишите метод `calculate_folder_size(path)`, который принимает путь до директории и возвращает размер папки в гигабайтах, округленный до 2 символов после запятой

Рекоммендации:

* Программы должна отлавливать все возможные ошибки (например, если передан несуществующий путь)
