Раздача что за папка
Возвращаемся на раздачу или как сделать невозможное
Предыстория
Не так давно, а именно 5 июня хабрачеловек по имени alan008 задал вопрос. Чтобы не заставлять ходить за подробностями, приведу его здесь:
При необходимости (если это требуется) можно снова поместить все файлы данных в один каталог на одном логическом диске.
В обсуждениях сошлись на том, что если это и можно сделать, то только ручками. Мне же этот вопрос показался интересным, и после возвращения из отпуска я нашел время, чтобы в нем разобраться.
В данном случае имеется отличный способ решения — перекачать заново. Но мы же не ищем легких путей, да и предлагался такой вариант! Итак, будем решать задачу по сложному — не скачивать.
Приступая к написанию любой программы, необходимо сначала продумать хотябы базовый алгоритм ее работы. В нашем случае алгоритм, по сути, состоит из двух шагов:
Ну что же, приступим к решению поставленной задачи.
Ищем торренты и читаем их
Для хранения информации из прочитанного файла будем использовать такой класс Torrent :
Здесь поля хранят следующую информацию:
Подготовив структуры для хранения необходимой информации, можно приступать к их заполнению.
Далее из этого блока необходимо забрать данные об имени торрента, размере куска.
В этом месте мы передаем в метод `BencodingUtils.DecodeFile` вторым параметром информацию о кодировке. Это как раз тот момент, когда пришлось добавлять один метод в библиотеку — изначально codepage-437 была вшита в код.
Тут все просто — имя торрента совпадает с именем файла. В случае, когда файлов в раздаче много, то в поле name пишется имя папки, в которую их надо положить (на самом деле может быть что угодно, но почему-то все пишут имя папки в которой эти файлы лежали при создании). Кроме того появляется список files в котором содержится информация о каждом файле: путь к нему и размер. Если размер — просто целое число, то путь к файлу представляет собой список из строк (имен директорий), пройдя по которым мы увидим этот файл.
Когда мы все это дело прочитали и посчитали — давайте создадим и вернем экземпляр Torrent :
Теперь, когда у нас есть все необходимые данные, мы готовы к самому интересному — поиску наших файлов.
Ищем файлы
Мы вплотную подошли к реализации второго шага нашего алгоритма. Для этого будем использовать метод FindFiles такого вида:
Здесь files — список файлов, среди которых мы будем искать, destinationPath — путь до папки назначения, в которую будут помещаться найденные файлы.
Также стоит проверять длину файла, но это уже сомнительно и иногда может давать ложные срабатывания. Только после того, как мы отсеяли по расширению явно левые файлы, можно приступать к проверке хеша.
После того как проверка завершена, и мы удостоверились в соответствии файла искомому — перемещаем его в папку назначения с правильным путем. Перед перемещением будем естественно проверять наличие директории, а также проверим есть ли уже такой файл или нет. copyFile — переменная передаваемая с формы пользователем, ее назначение, я думаю, понятно всем.
Есть в коде выше три важных для пояснения момента. Начну с двух последних — вот эти строки:
Теперь про первый момент. Я знаю про наличие foreach для списка, но его при использовании нельзя модифицировать этот спикок, то есть мы не сможем удалять уже ненужные более элементы. Итак, собирая все выше описанное в один метод, имеем:
Ну вот! Самое вкусное.
Проверка хеша
Как видно из кода выше, для проверки хеша мы передаем имя файла на диске и номер файла в списке файлов торрента. Это надо для того, чтобы не запускать поиск в списке файлов, а сразу взять его по номеру, раз он известен (еще одно «+1» циклу for ).
Теперь приступим к реализации нашего метода проверки хеша. На данном этапе мы знаем номер в списке файлов торрента и путь до файла на диске
Когда торрент-клиент проводит хеширование файлов, он считает хеш по порядку, однако бывает так, что нет одного или нескольких файлов. Тогда торрент-клиенту надо знать какой следующий кусок брать и откуда он будет начинаться в следующем имеющемся файле. Для вычисления двух этих цифр будем использовать следующий код, в котором переменная firstChunkNumber содержит номер первого куска, который полностью содержится в данном файле, а bytesOverhead — количество байт от начала файла до начала этого куска. Для лучшего понимания этого момента взгляните на поясняющий рисунок после кода.
Ответить на вопрос «Почему номер куска разный для случая, когда его начало совпадает с началом файла, и для случая, когда кусок лежит внутри?» предлагается самостоятельно.
Сейчас, зная номер куска мы должны взять его хеш из торрента с помощью такой конструкции:
После этого, надо прочитать кусок из файла и посчитать его хеш:
Собирая воедино сие творение возбужденного мозга, получим метод следующего содержания:
На этой прекрасной ноте, рассказ о методах и алгоритмах заканчивается, и мы переходим к рассказу о реализации в реальной жизни данного творения. Вполне понятно, что данная задача мной решалась не для того, чтобы решить, а для того, чтобы реализовать. Поэтому привожу на суд общественности мое творение, которое реализует все то, о чем написано выше.
Программа
По поводу производительности. Она пока что низкая: обработка 10 больших torrent-файлов заняла около 5 минут.
Не ищите торренты рекурсивно, если они собраны в корне диска.
Копирование может занять много времени, поэтому интерфейс может подвиснуть — не пугайтесь.
Так как писалась эта программа 4fun, то качество кода там немного не то, которое хотелось бы, но у меня оно работает. Данная программа не тестировалась, исправлялись только очевидные ошибки, поэтому могут быть, да что скрывать-то, есть скрытые баги. ИСПОЛЬЗУЯ ДАННУЮ ПРОГРАММУ, ВЫ ИСПОЛЬЗУЕТЕ ЕЕ НА СВОЙ СТРАХ И РИСК.
Взять исходники можно на github. Распространяется по GPLv2. Там есть архив с исполняемым файлом. Для работы требуется библиотека Bencode Library, но не оригинальная, а модифицированная мною (есть у меня в репозитарии, подключена субмодулем).
Спасибо всем, кто проявил терпение и дочитал эту статью до конца. Рад услышать ваши вопросы, приветствуется всевозможная помощь в совершенствовании алгоритма и, в особенности, кода.
UPD1. По результатам обсуждения мне стало понятно, что правильней будет не ломать существующие коллекции выдергиванием файлов на раздачу, а наоборот — создавать хардлинки в нужном для раздачи месте на файлы внутри упорядоченных коллекций (фильмо и дискографий, например). В дальнейшем программа будет работать именно так.
UPD2. Если у тех, кто пользовался этой утилитой, есть еще какие-то пожелания по функционалу или баг репорты, то прошу оставлять их на github в issue-трекере.