Упрощаем жизнь: сервис автораспаковки архивов на C#

    hateЭто, конечно, не статья, а небольшая путевая заметка, но тем не менее. Так получилось, что 99% архивов попадают на мой компьютер, чтобы быть тут же распакованными, дабы добраться до их содержимого. И если в маке сафари сам это делает за меня, то в windows приходится каждый раз нажимать пункт в контекстном меню.

    В какой-то момент мне это безумно надоело и я написал простой сервис, который распаковывает все самостоятельно. Мне он показался удобным и я решил поделиться с народом.


    Как это выглядит


    В системе есть сервис image

    Сервис смотрит за указаными папками и следит за созданием в них файлов с заданными расширениями(по умолчание rar и zip, задается параметром Extentions). Как только файл появился — запускается winrar(можно настроить и другой архиватор), который их распаковывает.

    Как это работает


    Настраивается все через конфиг monitors.cfg в формате ini файла
    [WinRar]
    c:\Program Files\WinRAR\WinRAR.exe

    [Folders]
    c:\downloads
    c:\distrib

    Конфиг можно править на лету, сервис сам подгружает из него изменения, ничего перезагружать не надо.

    За файловой системой следим через FileSystemWatcher
    1. foreach (var watcher in monitoringFolders.Where(Directory.Exists).Select(folder => new FileSystemWatcher(folder) {IncludeSubdirectories = true}))
    2. {
    3.   watcher.Created += WatcherHandler;
    4.   _watchers.Add(watcher);
    5. }
    * This source code was highlighted with Source Code Highlighter.


    Winrar запускается с ключами x -ad -o+ — распаковка в папку с таким же именем с заменой файлов.

    Как установить

    • Скачать и распаковать отсюда.
    • Запустить install.bat из папки AE.Service под правами администратора. Он создаст сервис через sc create(кстати, кто-нибудь может мне ответить, почему я могу создавать/удалять сервисы только через командную строку?). А если хочется просто протестировать без установки, то есть простое консольное приложение в папке AE.Console.

    Ссылки

    1. Исходники
    2. Бинарники
    Спасибо за внимание.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 58

      0
      все гениальное просто :)
      спасибо, возьму на заметку
        +7
        а что будет, если файл создан, но продолжает, скажем, загружаться?
          +6
          Хороший вопрос. Я это предусмотрел — в обработчике FileSystemWatcher файл пытается открыться на чтение. Если он еще загружается — получаем эксепшен(виндовая ошибка ERROR_SHARING_VIOLATION). Прога уходить в слип на секунду, после чего пытается еще раз открыть ну и т.д. Если за установленое количество попыток ничего не получилось — оставляем архив в покое, пусть кто-нибудь другой разбирается. Для себя количество попыток я установил до дури(int.MaxValue), вы можете поставить другое значение.

          Собственно код вы можете найти в функции WatcherHandler
          IOUtils.WrapSharingViolations(() => { using (File.OpenRead(file));}, null, int.MaxValue, 1000);

          * This source code was highlighted with Source Code Highlighter.

          И в утилитном класс IOUtils, там, правда, немного мутно из-за необходимости отличать ERROR_SHARING_VIOLATION в IOException от остальных ошибок доступа.
            +1
            Там бы еще неплохо многопоточную обработку вотчера прикрутить, чтобы не подвисало в ожидании закачки, но пока пользователей(а пока пользователь только я=)) все и так устраивает.
              0
              А вызовы раз в секунду не слишком грузят компьютер?
              И еще: с запароленными архивами проблем нет?
                +2
                Раз в секунду — нет, не грузят. Один кол OpenFile в раз секунду вы даже на таскменеджере не увидите.

                И еще: с запароленными архивами проблем нет?

                Подозреваю, что винрар просто отвалиться с ошибкой распаковки. Но я не проверял — утром посмотрю.
            0
            как происходит распаковка, когда в архиве запакована папка?
            папки в папке не будет?
              0
              кажется, это зависит от настроек архиватора, дефолтный профиль какой смотря вызвать.
              +1
              как происходит распаковка, когда в архиве запакована папка?

              Ну если в архиве лежит папка, то будет в результате папка с подпапкой — имя_архива/имя_подпапки. Так же, как если через контекстное меню распаковать.
                +1
                Надо еще сделать так, чтобы он удалял архив после распаковки. Т.е. запаковываешь что-нибудь в архив, а оно распаковывает обратно, да еще и архив сносит)) Прекрасное издевательство над пользователями.
                  0
                  Полагаю, запаковывать что-либо нужно в любую другую папку, отличную от тех, что указаны в настройках сервиса. Те папки исключительно для распаковки.
                  +2
                  Надо еще сделать так, чтобы он удалял архив после распаковки.

                  Я думал над этим, но с другой стороны кому-то может понадобиться сохранить архив. Например я скачал архив с исходниками, поковырялся с ними и грохнул папку, а архив остался на всякий случай, если снова к ним вернусь. Думаю, может вынести это опцией, но по умолчанию лучше оставлять — удаление операция опасная ввиду нетранзакционности файловой системы на большинстве десктопов.
                    +3
                    главное не скачивать рекурсивные архивы
                    или предусмотрена какая-нибудь защита?
                      0
                      Боюсь, что защиты нет. Я думал над этим — можно считать хэш-архива и запрещать повторную распаковку, но в некоторых случаях это будет багом для пользователя. Еще можно проанализировать структуру архива на предмет наличия таких бомб, но это уже совсем другой уровень сложности, может позже, когда будет время и желание покопаться в разных форматах архивов. Ну или отказаться от автораспаковки вложенных архивов.
                        0
                        распаковка во вложенную папку? во вложенных папках архивы не распаковывать?
                        После распаковки в альтернативный поток добавлять метку о том что файл распакован автоматом и больше не распаковывать
                          0
                          во вложенных папках архивы не распаковывать?


                          Ну я об этом и написал «Ну или отказаться от автораспаковки вложенных архивов.». Но опять же, в нормальной работе(где мы не скачиваем рекурсивные архивы), это может считаться недостатком.

                          После распаковки в альтернативный поток добавлять метку о том что файл распакован автоматом и больше не распаковывать


                          Хм. Так я же и написал про «запрещать повторную распаковку». Это может быть плохой вариант — например, я скачал архив, он распаковался, все хорошо. Прошло полгода, я обо всем забыл, заново качаю архив, но в другую папку(которая тоже мониторится). У меня ничего не распаковалось — я пишу разработчику багу. Можно, конечно, ограничить время жизнь хранения хэша архива — например один час. Но все равно, потенциально проблема есть.
                        +1
                        Я слишком поздно понимаю, чем мне знакомо это разноцветье.
                        Глубина-глубина…
                        Слишком поздно.
                        Дип-программа накрывает меня, и увернуться нет сил.
                        Глубина-глубина…
                        А полотнище все полыхает, не собираясь гаснуть, как положено честной, законопослушной дип-программе…
                        Глубина-глубина…
                        Я ныряю все глубже, я падаю в эту цветную пропасть, в бесконечную череду фальшивых отражений, в цветной лабиринт, в безумие и беспамятство.
                        На моей машине нет таймера, и никто не придет к моей двери со своим ключом.
                        Глубина-глубина…
                        Я не могу выныривать с такой скоростью, с какой затягивает цветной водоворот!
                        Глубина-глубина...
                        +2
                        В ОС основанных на ядре Linux это делается так:
                        1. Устанавливаем unp и убеждаемся в наличии inotify.
                        2. Запускаем из необходимых каталогов следующий скрипт (не тестировалось):
                        while true; do
                        inotifywait -e modify -e moved_to -e create "`pwd`"
                        unp *
                        sleep 10 # Во избежание убивания процессра постоянной попыткой распаковать качающийся файл.
                        done

                        В других *nix inotify заменяется на имеющееся средство, либо на кроссплатформенную обёртку FAM.
                          +1
                          Плюс сервиса в автозагрузке и подъеме системой в случае падения. Ну и к сессии пользователя они не привязаны. Я конечно еще тот знаток никсов, но мне казалось, что если проводить аналогию, это должен быть демон, прописанный в /etc/rc.local и кроновская таска, которая будем за ним следить, поднимая когда он упадет, разве нет? Ну и с точке зрения использования, мне было бы удобнее прописать пути в одном конфиге, чем запускать скрипт из нескольких папок.

                          Опять же, возможно я не прав, но разве эта команда — unp * — не будет каждый раз, когда мы скачаем новый архив, перераспаковывать заново все уже существующие архивы в папке?
                            –1
                            «Плюс сервиса в автозагрузке и подъеме системой в случае падения.»
                            Что-то я не замечал за windows-сервисами подобного. Обычно если он лег, то он лег, и система его не перезапускает.
                              0
                              Вроде как на 2k3 была такая фича.
                              0
                              Я же говорю — не тестировалось. :)

                              Возможно надо делать так:
                              files="*.{zip,rar,tar*, и т.д.}
                              unp $files
                              rm $files # или mv $files unpacked/

                              > если проводить аналогию

                              Зачем делать сложно если ту же задачу можно решить просто? Да, я понимаю что автру возможно захотелось поупражняться в написании служб на C#.
                                0
                                По поводу простоты — там в проекте лежит и консольное приложение — запускай и все. А если начать вешать доп. требования — автозапуск, настройка из одного места, параллельная распаковка, защита от рекурсивных архивов — то решение сразу усложнится и возможно написать его на том же питоне/C#/Lisp/ваш_любимый_язык_программирования и оформить демоном будет проще.
                                  0
                                  Я тут еще подумал
                                  inotifywait -e modify -e moved_to -e create "`pwd`"
                                  Вот эта штука будет реагировать на создание/изменению любых файлов, что не очень хорошо: я создал текстовый файл — у меня пошли распаковываться все архивы в папке. А еще в скрипте вложенные архивы не обрабатываются.
                              0
                              Вообще замечательная вещь, как правило во время закачки архвам присваивается временное расширение типа *.!ut так что использование его не так страшно, но все таки хотелось бы защиты от ошибок.
                              Будет ли данный сервис дорабатываться? Хотелось бы еще опцию автоудаления (если ее нет).
                              Удачи автору в начинании :)
                                +1
                                Будет ли данный сервис дорабатываться?
                                Будет, если это будет кому-то нужно.
                                Хотелось бы еще опцию автоудаления (если ее нет).
                                Сейчас залил новую версию и исходники по мотивам(ссылка в статье):
                                1. Добавлена опция автоудаления. По умолчанию автоудаление отключено.
                                  [Options]
                                  Autodelete = yes
                                2. Сделана многопоточная обработка — если скачалось много архивов, они начинают распаковывать параллельно.
                                3. Сделана простая защита от рекурсивных архивов — если архив распакован, повторная распаковка может быть запущена только через 10 минут.
                                  0
                                  По-хорошему бы батник копировал файлы куда надо еще… а то я вижу что он сейчас только устанавливает сервис из текущей папки
                                    +1
                                    А куда надо? Я могу и инсталлятор сделать, если нужно.
                                    Прогресс бар бы все таки не помешал, или хоть какоето оповещение: Началась распаковка, Успешно распакован, Ошибка, и т.д.

                                    Мне кажется это лишним. Программа должна работать незаметно для пользователя. Я скачал архив — пошел в папку — посмотрел содержимое. А так, что-то всплывает, мигает, рассказывает о своих ошибках и успехах, забирает фокус — мне такие программы не очень нравятся.
                                    И да, еще, [WinRar] это както не правильно :) Заменить бы на [Архиватор] и в том же разделе указывать параметры было бы неплохо

                                    Ну это можно =) Изначально параметры не стал выносить, т.к. хотелось сделать конфиг простым.
                                      0
                                      Ну хотя бы ведение лога опять же опционально, вместо оповещения…
                                      И в том архиве что поставляется хорошо бы указать все возможные параметры с комментариями. Не нашел где указать Extentions в Options?
                                      И почемуто с 7zip не работает на Win 7 x64
                                        +1
                                        Extentions указывается так

                                        [Extentions]
                                        rar,zip,7z


                                        Но вы правы, перенесу в Options.
                                        И в том архиве что поставляется хорошо бы указать все возможные параметры с комментариями.
                                        ok
                                        И почемуто с 7zip не работает на Win 7 x64
                                        Ну наверно потому, что параметры командной строки не подходят. Секция называлась [WinRar] не спроста — я, ибо ленивый, дал возможность только указать место, где лежит winrar.

                                        Все замечания завтра постараюсь поправить.
                                          0
                                          Uninstall.bat еще приложите для ленивых, перед обновлением же удалять нужно :)
                                            0
                                            Не нужно. Можно просто остановить и заменить exe файл. После чего запустить сервис снова.
                                              0
                                              а, ну да, или так…
                                    0
                                    Прогресс бар бы все таки не помешал, или хоть какоето оповещение: Началась распаковка, Успешно распакован, Ошибка, и т.д.
                                      0
                                      И да, еще, [WinRar] это както не правильно :) Заменить бы на [Архиватор] и в том же разделе указывать параметры было бы неплохо
                                  0
                                  «кстати, кто-нибудь может мне ответить, почему я могу создавать/удалять сервисы только через командную строку?»
                                  Никто не может вам ответить, потому что это неправда. В .net есть специальный компонент для этого, который автоматически создается в проекте при создании проекта сервиса. И в msdn про это написано.
                                    0
                                    В .net есть специальный компонент для этого, который автоматически создается в проекте при создании проекта сервиса

                                    Вы не правильно поняли вопрос. Я спрашиваю почему в консоли services.msc я не могу создать или удалить сервис, хотя могу сделать это через командную строку.
                                      +1
                                      Потому что незачем, если коротко. Установить сервис — задача того, кто его поставляет.
                                        0
                                        Надеюсь, что уловил суть вопроса. Возможно, нужно только дописать класс инсталлера. Если хотите создать установшик — вот руководство: wladm.narod.ru/C_Sharp/services.html

                                        Кратко: в проект дописать класс инсталлера
                                        using System.ComponentModel;
                                        using System.Configuration.Install;
                                        using System.ServiceProcess;

                                        namespace AE.Service
                                        {
                                          [RunInstaller(true)]
                                          public partial class ExtractorServiceInstaller : Installer
                                          {
                                            public ExtractorServiceInstaller()
                                            {
                                              var serviceProcessInstaller =
                                                new ServiceProcessInstaller
                                                {
                                                  Account = ServiceAccount.LocalSystem,
                                                  Username = null,
                                                  Password = null
                                                };
                                              var serviceInstaller =
                                                new ServiceInstaller
                                                {
                                                  ServiceName = "My Windows Service",
                                                  DisplayName = "My New C# Windows Service",
                                                  StartType = ServiceStartMode.Automatic
                                                };

                                              Installers.AddRange(new Installer[] { serviceProcessInstaller, serviceInstaller });
                                            }
                                          }
                                        }


                                        * This source code was highlighted with Source Code Highlighter.

                                        добавить к решению проект типа Setup Project, контекстное->View->Custom Actions; для Install и Uninstall через их контекстное меню выполняем пункт Add Custom Action и указываем System Folder, проследить, чтобы свойство Installer Class у этих Custom Action было выставлено в True.
                                          0
                                          Да нет, я знаю как сделать установщик. Батник я сделал потому что:

                                          1) Так было проще
                                          2) Хотел показать(может кто не знает), как пользоваться sc.

                                          А вопрос был про то, как мне уже существующие сервисы, как админу, удалить с компа.

                                          Потому что незачем, если коротко. Установить сервис — задача того, кто его поставляет.

                                          Ну во-первых раз у меня возник этот вопрос, значит case есть. Во-вторых, если «задача того, кто его поставляет», чего же тогда в sc у меня есть возможность снести или добавить любой сервис, который мне не нравится? По сути я говорю об отсутствии UI к команде sc(вернее к тому API, который она дергает).
                                            +1
                                            Ну так через установщик можно и удалить.
                                              0
                                              Ну это же разные вещи. Я говорю про то, что в консоли у меня больше возможностей управления системой, чем в UI и это на мой взгляд не правильно в рамках windows-way, а вы говорите про установщик.
                                              0
                                              «Ну во-первых раз у меня возник этот вопрос, значит case есть.»
                                                0
                                                «Ну во-первых раз у меня возник этот вопрос, значит case есть.»
                                                Вот для него и есть sc.

                                                «По сути я говорю об отсутствии UI к команде sc(вернее к тому API, который она дергает). „
                                                Не на всякую чисто административную (и редко используемую) команду есть UI. Это, в среднем, нормально (да, даже в Windows).

                                                Типовое поведение: поставить сервис через инсталлятор, снести через деинсталлятор. Если по типовому сценарию не прошло — тогда есть sc.
                                                  0
                                                  Не на всякую чисто административную (и редко используемую) команду есть UI. Это, в среднем, нормально (да, даже в Windows).
                                                  Я считаю это вопрос концептуальной целостности. Через services.msc я могу остановить сервис, я могу изменить ему права, я могу сделать так, чтобы он никогда больше не запустился. Вроде как центральная точка управления службами. Но для удаления я должен пользоваться деинсталятором(который мне снесет весь пакет продукта, а не только сервис) либо лезть в командную строку.
                                                    0
                                                    «Вроде как центральная точка управления службами. „
                                                    Установленными.

                                                    “Но для удаления я должен пользоваться деинсталятором(который мне снесет весь пакет продукта, а не только сервис)»
                                                    Да, именно так. Потому что только деинсталлятор знает, что будет, если вы снесете только сервис, а все остальное оставите.
                                                      0
                                                      Будет все тоже самое, как если бы я его просто остановил навсегда или убил из таскменеджера. Нет?
                                                        0
                                                        Не совсем. Его все еще можно достать через сервис-менеджер.
                                                  0
                                                  Выслал пример вам личкой
                                            0
                                            А как решена проблема setup.exe и других подобных имен файлов, часто встречающихся в архивах?
                                              0
                                              Простите, о какой проблеме идет речь?
                                                0
                                                Наверное речь идет о том, что в папке распаковки может оказаться два архива, в которых могут лежать файлы с одинаковым именем и расширением, но реально содержащих разные данные.
                                                Ну и при автоматической распаковке файл из одного архива будет затерт файлом из другого.
                                                  0
                                                  Ну распаковка идет в папку с именем архива. Так что такая ситуация возникает только если и архивы у нас называются одинаково.
                                              0
                                              Хм. А зачем понадобились ini-файлы в 2011 году? Чем стандартный App.config не устроил? :)
                                                0
                                                На мой взгляд ini читается человеком проще, чем xml.
                                                0
                                                Когда же обновление? :)
                                                  +1
                                                  Я сейчас немножко уехал в отпуск, а до этого сдавал дела. Постараюсь около 20-го обновить.
                                                    0
                                                    Главное чтобы не забросили проект! :)

                                                Only users with full accounts can post comments. Log in, please.