Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Как я писал модуль обновления на C#

Anna | 18.06.2014 | нет комментариев

Я пишу программы на C# для фирмы, где их использует несколько сотен человек. Время от времени добавляются новые функции и встаёт задача обновления версий.

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

image

Добросовестно, мне самому не дюже нравятся приложения, которые постоянно скачивают обновления, но в моём случае проще автоматизировать данный процесс, чем писать должностные инструкции и принуждать коллег скачивать обновления вручную (а потом бегать по каждому этажам и делать это самому).

Цели

Раньше чем начать работу над этим планом, я сформулировал цели, которым должен удовлетворять грядущий модуль авто-обновления.

  1. Обновление должно протекать механически при наличии новой версии.
  2. Позже обновления программа должна механически перезапуститься.
  3. Позже обновления имя программы должно сохраниться бывшим.
  4. Модуль должен встраиваться в ехе-файл плана.

И в чём, казалось бы, задача? Проверил присутствие новой версии. Скачал файл. Запустил. Всё!

Задача становится немножко запутанной из-за того, что запущенный ехе-файл не может сам себя удалять либо переименовывать, это должен делать иной процесс при закрытой программе.

А для выполнения всех нужных действий в рамках одной программы:

  • скачать новую версию,
  • удалить ветхую программу,
  • переименовать скачанный файл

– нужно несколько раз её перезапускать, Дабы ступенчато исполнять перечисленные действия.

Блок-схема

Дабы прояснить, на каком этапе что необходимо делать, я составил блок-схему каждого процесса.
По собственному навыку знаю, что на составление схемы требуется час, а экономится день.

image
Схемы я неизменно рисую от руки, перерисовывая их по несколько раз, всякий раз добавляются новые детали, да и сам процесс мне дюже нравится.

Этапы

На блок-схеме выделены три этапа процесса обновления.

Этап А. Программа запущена в обыкновенном режиме (без ключей).
get up_version
Считываем и проверяем номер версии на сервере.

my_version == up_version?
Если серверная версия совпадает с нашей – пропускаем модуль обновления.

download new.name.exe
Закачиваем новую программу в файл new.name.exe.

% % %% % %
Ждем окончание процесса загрузки.

start new.name.exe /u
Позже окончания загрузки запускаем скачанный файл.

Закрываем программу, Дабы потом её удалить.

Этап Б. Программа запущенна с ключом /u.
del name.exe
Удаляем программу name.exe.

copy new.name.exe name.exe
Копируем new.name.exe в name.exe.

start name.exe /d
Запускаем name.exe с ключом /d.

Закрываем программу, Дабы потом её удалить.

Этап Ц. Программа запущенна с ключом /d:
del new.name.exe
Удаляем временную копию программы new.name.exe

Запускаем основную программу.

Сейчас переходим к утилитарной части, как я это всё реализовал в классе на C#.

Основные поля данных

Начнём обзор модуля со знакомства с полями данных, то есть с рабочими константами и переменными.

    // Нынешняя версия плана, доступная для каждого плана
    public static string my_version = "1.23";

    // Ссылки на txt-файл версии, на exe-файл программы и на сайт
    private string url_version = "http://localhost/version.txt";
    private string url_program = "http://localhost/program.exe";
    private string url_foruser = "http://localhost/index.php";

    private string my_filename;   // Имя файла запущенной программы 
    private string up_filename;  // Имя временного файла для загрузки обновления

    private bool is_download;   // Знак, что началось скачивание обновления
    private bool is_skipped;   // Знак, что обновление не требуется либо завершено

Запуск!

Работа модуля начинается с его запуска. В каком месте программы это сделать отменнее каждого? Я перепробовал различные варианты, и самым успешным мне показался вариант его запуска из файлаProgram.cs

    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        // Инициализация и запуск модуля обновления
        FormUpdate up = new FormUpdate();

        if (up.download())   // Если началось скачивание 
            Application.Run(up); // … ждем его окончания

        if (up.skipped())    // Обновление не требуется либо завершено
            Application.Run(new Form1()); // … запускаем основную программу
    }

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

Способ download() сообщает о том, что на этапе «А» началось асинхронное скачивание новой версии программы, в связи с чем необходимо отобразить визуальную форму, на которой размещён ProgressBar с текстовым полем, и ожидать заключения процесса. Остальные этапы обновления выполняются «безмолвно» и отображение формы пропускается.

Способ skipped() сообщает о том, что обновление пропущено (либо завершено), программа может быть запущенна в обыкновенном режиме. В другом случае программа должна закрыться для перехода к дальнейшему этапу.

Конструктор

Напишем конструктор класса, тот, что по доводам командной строки установит, на каком этапе обновления мы находимся, и вызовет соответствующие способы класса.

    private FormUpdater()
    {
    	// Получаем имя запущенной программы (без полного пути)
        my_filename = get_exec_filename (); 

        // Формируем имя временного файла
        up_filename = "new."   my_filename; 

        // Получаем доводы командной строки
        string [] keys = Environment.GetCommandLineArgs(); 

        if (keys.Length < 3)   // Этап А. Доводов нет – проверим версию на сервере
            do_check_update ();
        else
        {
            if (keys[1] == "/u")  // Этап Б. Запущена новая версия из временного файла
                do_copy_downloaded_program (keys [2]);

            if (keys[1] == "/d")  // Этап Ц. Осталось удалить непостоянный файл.
                do_delete_old_program (keys [2]);
        }
    }

Несколько слов о вспомогательном способе get_exec_filename(). В C# дозволено получить имя запущенного файла только с полным путём. Для изъятия чистого имени файла я написал свой способ, тот, что разбивает путь на части по символу «» и возвращает последнюю его часть – желанное имя файла.

    private string get_exec_filename()
    {
        string fullname = Application.ExecutablePath; 
        // Скажем: D:WorkProjectsName.exe
        string[] split = { "\" };
        string[] parts = fullname.Split(split, StringSplitOptions.None);
        // Получим массив из 4 элементов: D: , Work , Projects , Name.exe

        if (parts.Length > 0)
            return parts[parts.Length - 1]; // Конечный элемент = желанное имя файла
        return "";
    }

Остальные способы, которые вызываются из конструктора, будут рассмотрены на соответствующих этапах.

Этап «А»

Способ do_check_update() проверяет присутствие обновления на сервере и, в зависимости от итога, либо запускает процесс обновления, либо разрешает запуск стержневой программы.

    private void do_check_update()
    {
        // получаем номер версии программы на сервере
        string up_version = get_server_version(); 

        if (my_version == up_version) // Если обновление не необходимо
        {
            is_download = false; // Пропускаем скачивание
            is_skipped = true;   // Пропускаем модуль обновления
        } else
            do_download_update ();   // Запускаем скачивание новой версии
    }

Способ get_server_version() использует стандартный способ класса WebClient для считывания номера версии.
Если номер версии не считывается, разумно предположить, что обновление тоже не удастся скачать, следственно будем считать, что обновления нет.

    private string get_server_version()
    {
        try {
            WebClient webClient = new WebClient(); 
            return webClient.DownloadString(url_version).Trim();
        } catch {     // Если номер версии не можем получить,
            return my_version;  // то программу даже и не будем пытаться.
        }
    }

Способ do_download_update() отображает экранную форму и запускает асинхронную загрузку обновлённого файла программы.

    private void do_download_update ()
    {
        InitializeComponent();      // Инициализация формы
        label_status.Text = "Скачивается файл: "   url_program;
        download_file (); // Начинаем скачивание
        is_download = true;  // Будем ожидать заключение процесса 
        is_skipped = false;   // Основную программу не необходимо запускать
    }

Способ download_file() запускает асинхронное скачивание и подключает два события:
для отображения прогресса и для заключения этапа загрузки файла.

    private void download_file ()
    {
        try
        {
            WebClient webClient = new WebClient(); 
            // Создаём обработчики событий движения прогресса и его окончания
            webClient.DownloadProgressChanged  = new DownloadProgressChangedEventHandler(ProgressChanged);
            webClient.DownloadFileCompleted  = new AsyncCompletedEventHandler(Completed);

            // Начинаем скачивание
            webClient.DownloadFileAsync(new Uri(url_program), up_filename);
        }
        catch (Exception ex) 
        {  // В случае ошибки выводим сообщение и предлагаем скачать вручную
            error(ex.Message   " "   filename); 
        }
    }

В случае ошибки вызывается способ error(), тот, что отображает текст ошибки и предлагает скачать файл самосильно, в этом случае будет запущен браузер и открыта соответствующая страница. Текст этого способа, думаю, дозволено опустить.

Из этапа «А» осталось реализовать обработку 2-х событий:
метаморфоза прогресса скачивания и обработку его окончания.

Способ метаморфозы прогресса написан банально.

    private void ProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
        progress_download.Value = e.ProgressPercentage;
    }

По заключению скачивания нужно перейти к этапу «Б» и закончить работу.

    private void Completed(object sender, AsyncCompletedEventArgs e)
    {
        run_program(up_filename, "/u ""   my_filename   """);
        this.Close ();
    }

Обратите внимание, что помимо ключа /u в программу передаётся начальное имя программы, Дабы опять запущенная программа знала, как переименовывать файл, согласно 3-ей цели: позже обновления имя программы должно сохраниться бывшим.

Имя файла в параметре командной строки нужно заключать в кавычки на случай наличия в нём пробелов.

Поля способа is_downloadis_skipped в этом способе устанавливать не необходимо, так как этап их проверки в файле Program.cs был пройден сразу позже запуска скачивания.

Способ для запуска программы может выглядеть дальнейшим образом.

    private void run_program(string filename, string keys)
    {
        try
        {   // Применение системных способов для запуска программы
            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo.WorkingDirectory = Application.StartupPath;
            proc.StartInfo.FileName = filename;
            proc.StartInfo.Arguments = keys; // Доводы командной строки
            proc.Start(); // Запускаем!
        }
        catch (Exception ex)
        {
            error(ex.Message   " "   filename);
        }
    }

Выходит, с этапом «А» мы разобрались.

Если что-то показалось запутанным, рекомендую ещё раз просмотреть блок-схему и сопоставить способы модуля с элементами на блок-схеме.

Этап «Б»

Переходим к этапу «Б», он будет гораздо проще. Из конструктора вызывается способdo_copy_downloaded_program(string filename), тот, что копирует загруженную версию программы на место ветхой.

    void do_copy_downloaded_program(string filename)
    {
        try_to_delete_file(filename); // Удаляем файл со ветхой версией программы
        try
        {   // Копируем скачанный файл в подлинное имя файла
            File.Copy(my_filename, filename);

            // Запускаем этап «Ц»
            run_program(filename, "/d ""   my_filename   """); 
            is_download = false;  // Форма не отображается
            is_skipped = false;   // Обновление ещё не завершено
        }
        catch (Exception ex)
        {
            error(ex.Message   " "   filename);
        }
    }

Несколько слов о способе try_to_delete_file(string filename). Может оказаться так, что мы пытаемся удалить файл, тот, что ещё заблокирован не до конца завершённым процессом предыдущей программы. Данный способ пытается удалять файл несколько раз подряд в течении нескольких секунд с небольшими задержками.

    private void try_to_delete_file(string filename)
    {
        int loop = 10; // число попыток 
        while (--loop > 0 && File.Exists(filename))
            try { 
                File.Delete(filename);
            } catch {
                Thread.Sleep(200); // Маленькая задержка
            }
    }

Этап «Ц»

Остался конечный, самый короткий этап «Ц», тот, что удаляет «мусор» и запускает основную программу. Для этой цели из конструктора вызывается способ do_delete_old_program(string filename).

    void do_delete_old_program(string filename)
    {
        try_to_delete_file(filename);
        is_download = false;  // Форма не отображается
        is_skipped = true; // Обновление отработало, запускайте!
    }

Если версия программы на сервере не будет совпадать с записанной версией в текстовом файле, то при всяком запуске будет вторично загружаться «новая» версия. Позже этого программа всё-таки будет запущена несмотря на отличие версий: по блок-схеме на этапе «Ц» версия программы теснее не проверяется. Так и должно быть! Добавочная проверка на этом этапе рискует зациклить процесс скачивания навечно…

Кстати, эту «фишку» дозволено применять для запуска программы без проверки наличия обновлений, довольно её запускать с ключом «/u».

Завершение

Построенный таким образом модуль обновления удовлетворяет каждому целям, сформулированным в начале статьи:

  1. Обновление скачивается только при наличии новой версии.
  2. Обновлённая программа механически запускается позже скачивания.
  3. Предусмотрен механизм сохранения начального имени файла программы.
  4. Модуль сделан не отдельной программой, а встроен в файл плана.

Работа модуля продемонстрирована на дальнейшем рисунке.
image

Также могу продемонстрировать работу модуля на собственной программе постижения английских слов на слух. Скачать «ветхую» версию «Звуковых карточек» дозволено тут:http://www.DoubleEnglish.ru/soft/old.ListenCards.exe

Начальный код модуля обновления в тестовом плане дозволено скачать тут:
http://www.fformula.net/docs/updater/updater.zip

Спасибо за внимание.
Волосатов Евгений.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB