
В последнее время я занимался расчисткой своих жестких дисков. Знаете это чувство, когда у вас есть папка backup внутри папки backup final, которая находится внутри еще одной папки backup final (1). У меня множество таких рекурсивных бэкапов, они мигрируют с одного диска на другой, с одного компьютера на другой.
Одна из самых больших директорий — это директория со всеми моими фотографиями и видео. Каждый бэкап содержит эту папку с фотографиями на всякий случай.
Итак, есть проблема. Контент скопирован множество раз, копии занимают гигабайты, с каждой итерацией становится все сложнее. У меня нет желания разбираться с этим вручную.
Я подумал, что это отличный пример проблемы, которую я могу решить с помощью своих навыков программирования и внести вклад в мир open-source. Просто небольшая утилита для пет-проекта, делающая всю эту работу. Её можно рассматривать как понятный пример для новичков о том, как написать консольную утилиту и предоставить её пользователям.
Более того, октябрь — это месяц Hacktoberfest. Самое время довести дела до конца.
Todo
Что нам нужно сделать? Давайте определим задачу.
- Читать файлы один за другим и копировать их в результирующую директорию, если файл уникален;
- Определять, уникален ли файл, используя кэшированные хэши всех прочитанных файлов;
- Клоны должны быть уничтожены в результирующей директории (именование важно, репликанты должны быть уничтожены).
Добавим несколько дополнительных задач.
- Получить список всех клонов без удаления;
- Возможность удалять клоны из заданной директории;
- Основная команда должна копировать все уникальные файлы из входной директории в выходную, пытаясь сохранить структуру директорий и имена файлов;
- Опция flatten для копирования всех уникальных файлов в одну директорию без сохранения структуры;
- Сделать возможным переименование скопированных файлов по какому-то оптимальному правилу, например по дате;
- Для файлов фотографий можно использовать дату, когда фото было сделано.
Инструменты
Я люблю Kotlin. Может показаться, что это не лучший язык для написания консольных утилит, но почему бы и нет? Используя Kotlin, я значительно сокращаю время на разработку. По моему опыту, пет-проекты часто забрасываются, потому что не хватает времени их закончить.
Я хотел создать консольную утилиту с простым синтаксисом с аргументами. Clikt отлично подходит для этого. Он делает написание интерфейса командной строки действительно интуитивным. Мне нравится, как он создает все эти справочные сообщения без моего участия. Программист просто пишет типизированные аргументы, а Clikt делает все остальное.
MetadataExtractor — отличная библиотека для работы с медиа-файлами. Основная цель bladerunner для меня — копировать уникальные фотографии, поэтому мне нужно что-то для работы с метаданными фотографий. Она используется для получения даты съемки фотографии, так что мне не нужно думать об этом.
Вот, в принципе, и все. В качестве вишенки на торте я хотел написать юнит-тесты. Junit5 и AssertJ — мой выбор для этого проекта. Библиотека Assert — это вопрос вашей привычки или личного чувства прекрасного.
Github Actions используется как мой CI/CD инструмент.
Синтаксис
Есть три основные команды
Run
Копирует все уникальные файлы в выходную директорию.
Usage: bladerunner run [OPTIONS]
Options:
-din, --directory-in DIRECTORY Path to root directory of input
-dout, --directory-out DIRECTORY
Path to output directory
-ns, --naming-strategy [DEFAULT|DATE_MODIFIED|PHOTO_TAKEN]
Naming strategy for created files
-o, --out FILE Path to output file
-s, --silent Do not log activity
-f, --flatten Copy all files into out directory without
saving directory tree
-h, --help Show this message and exit
Naming strategy
Пара слов о том, как создаются имена для скопированных файлов.
DEFAULTиспользует оригинальное имя файла.DEFAULT— это дефолтное поведение.DATE_MODIFIEDиспользует дату последней модификации файла.PHOTO_TAKENполучает дату съемки фотографии из EXIF файла, если возможно, иначе использует fallback.
Случайная UUID-строка добавляется как суффикс к имени файла, если в выходной директории уже есть файл с таким же именем.
Clean
Удаляет все неуникальные файлы из заданной директории.
Usage: bladerunner clean [OPTIONS]
Options:
-din, --directory-in DIRECTORY Path to root directory of input
-o, --out FILE Path to output file
-s, --silent Do not log activity
-h, --help Show this message and exit
Find
Выводит информацию о том, является ли файл клоном или нет, для всех файлов в заданной директории.
Usage: bladerunner find [OPTIONS]
Options:
-din, --directory-in DIRECTORY Path to root directory of input
-o, --out FILE Path to output file
-h, --help Show this message and exit
Результат
Теперь у меня есть хороший репозиторий pavelkorolevxyz/bladerunner-desktop в профиле. Последние артефакты bladerunner можно найти на странице Releases.
Каждый push в основную ветку разработки автоматически тестируется и собирается с помощью Github Actions. На данный момент проект выглядит довольно завершенным с точки зрения функционала, документации и тестов. Мне это нравится.
Наконец, самое главное — теперь у меня папки с бэкапами чистые и без тонн клонов.