воскресенье, 24 июня 2012 г.

Генерация Assembly Version при помощи T4 шаблонов

[Generation of Assembly Version using the T4 templates]

Assembly Version используется .NET фреймворком во время построения и во время исполнения приложения для определения загружаемой сборки. Другими словами, Assembly Version является важной частью имени сборки, ее однозначно определяющей.

Assembly File Version является номером версии файла (отображается в Windows Explorer) и НЕ ИСПОЛЬЗУЕТСЯ .NET-ран-таймом для локализации сборки.

Эти версии указываются в атрибутах сборки и как правило находятся в файле AssemblyInfo.cs:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


Если сборки создаются очень часто, ручное изменение значений указанных атрибутов становится накладным. Для автоматического увеличения номера построения (Build Number) и ревизии (Revision) можно использовать символ звездочки (*):
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.0.*")]


Компилятор сгенерирует номера самостоятельно, и этого в большинстве случаев будет достаточно. Но выдаваемые номера трудно поддаются интерпретации , и если хочется наделить числа большим смыслом, например, привязать к дате/времени, придется делать это при помощи сторонних средств. Для этих целей есть множество утилит и надстроек к Visual Studio в виде AddIn-ов, я же воспользуюсь встроенными средствами Visual Studio - генерацей кода по текстовым шаблонам: Text Template Transformation Toolkit, или коротко T4. При помощи этого инструмента можно генерировать заветные строки самостоятельно, наделив нумерацию необходимой логикой. У меня в примере нумерация привязана к году, месяцу, числу, и часу, когда создается сборка. Добавьте новый файл шаблона в проект, и назовите его, например, “AssemblyVersion.tt”:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
// Assembly Version
// Generated at <#= DateTime.Now #>
//
// [major version].[minor version].[build number].[revision number]
// build number = yy MM = Year Month (e.g.: 1102 = 2011 February)
// revision number = ddHH = Day Hour (e.g.: 1411 = 14 "February" at 11 )
//
// to generate for every build in BuildEvents:
// "%CommonProgramFiles%\Microsoft Shared\TextTemplating\10.0\TextTransform.exe" "$(ProjectDir)AssemblyVersion.tt" -out "$(ProjectDir)AssemblyVersion.cs"

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyVersion("1.0.<#= DateTime.Now.ToString("yyMM") #>.<#= DateTime.Now.ToString("ddHH") #>")]
[assembly: AssemblyFileVersion("1.0.<#= DateTime.Now.ToString("yyMM") #>.<#= DateTime.Now.ToString("ddHH") #>")]


Генерация кода по файлам шаблонов (*.tt) происходит автоматически при сохранении изменений в шаблоне или при явном вызове соответствующей команды (например из контекстного меню). Также можно попросить Visual Studio обработать все шалоны проекта. Но нам нужно добиться генерации при каждом построении (build) проекта. Для этого можно воспользоваться этим (http://msdn.microsoft.com/ru-ru/library/ee847423.aspx) руководством от MS "Создание кода в процессе построения".  Я же вызываю генератор шаблонов из командной строки в Prebuild-событии:

"%CommonProgramFiles%\Microsoft Shared\TextTemplating\10.0\TextTransform.exe" "$(ProjectDir)AssemblyVersion.tt" -out "$(ProjectDir)AssemblyVersion.cs"


Не забудьте удалить или закомментировать ненужные уже строки в файле AssemblyInfo.cs!


Замечания:
- Не уверен, но кажется, что генерация по шаблонам T4 не поддерживаются в Visual Studio Express - версии.
- Вам не обойтись от изменения версии файла (AssemblyFileVersion) если вы в проекте установки (Setup Project) используете параметр "RemovePreviousVersion=true". Файлы обновляются, только если имеют большую версию, чем уже установленые.
- Для проекта типа Class Library (Visual Basic) в Visual Studio 2010 автоматическая генерация версии при помощи знака (*) не срабатывает. Это известная проблема описана здесь (https://connect.microsoft.com/VisualStudio/feedback/details/588047), поэтому для автоматической генерации вам, наверное, придется использовать "внешние" средства, такие как T4.
- Если необходимо, чтобы все сборки проекта имели одинаковый номер версии, можно воспользоваться следующим способом:
    -- Удалите определяющие версию атрибуты во всех проектах (файлы AssemblyInfo).
    -- В одном из проектов создайте файл с атрибутами версии (AssemblyVersion, AssemblyFileVersion). Можно воспользоваться описанной в статье кодо-генерацией.
    -- Добавьте этот файл к каждому из проектов, но КАК ССЫЛКУ (as link).

------------------------------------------

Ресурсы:
How to use Assembly Version and Assembly File Version
http://support.microsoft.com/kb/556041/en-us

Создание кода и текстовые шаблоны T4
http://msdn.microsoft.com/ru-ru/library/bb126445

Official T4 team blog - официальный блог команды разработчиков t4
http://blogs.msdn.com/b/t4/
И их официальный блог примеров
http://t4talk.codeplex.com/

среда, 13 июня 2012 г.

Visual Studio Setup Project. Сохранение и восстановление установочного пути в реестре

[Visual Studio Setup Project. Reading and writing the installation path in the registry]

Setup Project в VisualStudio можно настроить так, чтобы перед установкой программы предыдущая версия удалялась: RemovePreviousVersion = true. На всякий случай напомню, что для корректной работы этого параметра необходимо увеличивать версию установочной программы - свойство Version.


Но есть одна неприятная вещь: путь, предлагаемый в диалоге выбора пути установки, полностью игнорирует выбор, сделанный пользователем в предыдущий раз. Установка удаляет старую версию программы и начинает новую практически с чистого листа. О сохранении каких-либо параметров нужно беспокоится самому. К счастью, есть возможность сделать это относительно просто, не написав, при этом, ни строчки кода. Решение проверено в немецкой Visual Studio 2010 (соответствующие “немецкие” скриншоты, не пугайтесь), уверен будет работать в VS 2008, и наверняка в VS 2005. Для этого нужно выполнить следующие шаги.

1. Установка пути по умолчанию. Во время первой установки необходимо предложить путь и здесь для вас ничего не меняется - определите DefaultLocation как вы это делали раньше (обычно это пусть к каталогу "Program Files" и далее в папку вашей фирмы и/или вашего продукта). Значение этого пути сохраняется во внутренней служебной переменой [TARGETDIR], пользователь может изменить его в диалоге выбора пути установки.


 2. Сохраняем путь в реестре. Для этого нужно определить создаваемую во время установки в реестре строку, например, как у меня под именем “HKEY_LOCAL_MACHINE\Software\[Manufacturer]\InstallPath”. Программа установки присвоит ей значение переменной “[TARGETDIR]”, которая будет содержать установочный путь.


3. Чтение пути из реестра. Для этого нужно определить "условие для запуска" (мой вольный перевод, лучше смотрите скриншот), которое читает реестр и сохраняет значение в определенной переменной. Установите в полях RegKey, Root и Value значения, которые вы задали для сохраняемого пути. И, наконец, трюк, который позволяет этому работать - в поле Property задайте имя переменной TARGETDIR.


PS. До этого решения мне помогла додуматься статья http://support.microsoft.com/kb/827026. Основное отличие от моего заключается в том, что в ней строковое значение реестра читается в пользовательскую переменную, которая присваивается полю “DefaultLocation” (свойство “пути приложения”). Но если ключа еще не существует (самая первая установка), путь остается не определен, и пользователь увидит, скорее всего, в диалоге выбора пути “C:\”.

вторник, 1 мая 2012 г.

Ubuntu, File Roller и крякозяблы в именах файлов

В Ubuntu для работы с RAR-архивами традиционно используются две программы: rar и unrar. Обе поддерживаются стандартным гномовским менеджером архивов - File Roller-ом. Первая, rar, предназначена для создания и распаковывания rar-архивов, но, к сожалению, не всегда справляется должным образом со второй задачей. В частности проблемы возникают при распаковке архивов созданных в Windows, где имена файлов/папок содержат русские буквы. Также, судя по постам в интернете, проблемы возникают и с зашифрованными архивами. Поэтому для распаковки, лучше всего воспользоваться программой unrar, вторым коммерческим продуктом создателя формата. Эта утилита прекрасно распаковывает rar-архивы, но, как следует из ее названия не предназначена для их создания. Для любителей всего свободного существует другая, но также несколько ограниченная версия распаковщика - unrar-free. Под свободной здесь понимается то, что проприетарный формат RAR и его основной закрытый функционал был исследован и реализован при помощи реверс-инжиниринга.


Для тех, кто, как я, предпочитает пользоваться графическим интерфейсом, в частности менеджером File Roller одновременное использование rar для упаковки и unrar для распаковки не представляется возможным. Если в системе установлен rar, наличие unrar просто игнорируется. Я даже глянул в исходники File Roller-а, чтобы убедиться в этом. Во всех местах, связанных с извлечением данных присутствует такой код:

if (have_rar ())
       fr_process_begin_command (comm->process, "rar");
else
       fr_process_begin_command (comm->process, "unrar");


Итог: Для приемлемой работы с RAR-архивами можно воспользоваться полумерой: установить unrar и удалить rar. Нужно лишь отказаться от создания RAR-архивов (в мире Linux это чуждый формат), в пользу другого, например zip.


суббота, 7 апреля 2012 г.

Меняем кодировку ID3 из cp1251 в Unicode. Ubuntu

Установка:
sudo apt-get install python-mutagen 

Создаем Id3v2 в кодировке utf8 из тега id3v1 и кодировки cp1251 для одного файла. Тег id3v1 удаляется:
mid3iconv -e CP1251 -d bad.mp3

То же, что и выше, но для всех файлов коллекции:
find /media/Music/ -iname "*.mp3" -print0 | xargs -0 mid3iconv -e CP1251 -d

Источник:

Вебсервер в Ubuntu в VirtualBox

Данное HowTo описывает процесс создания веб-сервера Ubuntu в виртуальной машине VirtualBox. Сервер был поднят мной для разработки и тестирования веб-приложений. Описание не претендует на полноценное руководство, описаны лишь основные шаги и признано лишь служить небольшой опорой, в первую очередь для меня самого, если этим когда-нибудь придется заняться еще раз.

У меня хост-система тоже Ubuntu (12.04 beta), но думаю, что это не играет какую-то важную роль, упомяну лишь, что VirtualBox 4.1.10 был установлен из репозиториев дистрибутива.

1. Веб-сервер - Ubuntu 10.04 LTS, новая установка.
2. Установка LAMP через Synaptic-Пакет-менеджер (Меню Edit=>установка пакетов по задачам=>LAMP server.
3. После установки заменил режим сети в VirtualBox с NAT на Bridged networking
4. Для создания нескольких виртуальных хостов на одном сервере использовал следующие руководства:
    - http://blog.axshavan.ru/2010/11/lamp-ubuntu.html
    - http://ubuntuforums.org/showthread.php?t=794248
    - http://wiki.ubuntuusers.de/Apache/Virtual_Hosts
    - http://barrierefrei.e-workers.de/know_virtual.php
    - ftp://ftp.ua.openwall.com/pub/docs/mirrors/pm4u.opennet.ru/apache.htm

- Скопировал файла /etc/apache2/sites-available/default в ту же папку
- Новый файл переименовал во что-то вроде этого “mysites” и отредактировал следующим образом:
<VirtualHost *:80>
  ServerName mysites 
  #ServerAlias можно указать альтернативные имена хоста
  ServerAdmin webmaster@localhost
  DocumentRoot /var/www/mysites/
  <Directory />
    Options FollowSymLinks
    AllowOverride None
  </Directory>
  <Directory /var/www/mysites>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Order allow,deny
    allow from all
  </Directory>
  ErrorLog /var/log/apache2/error.log
  # Possible values include: debug, info, notice, warn, error, crit,
  # alert, emerg.
  LogLevel warn
  CustomLog /var/log/apache2/access.log combined
</VirtualHost>


* This source code was highlighted with Source Code Highlighter.


- Создал папку “/var/www/mysites/” и в ней index-файл (html, php...)
- Прописал новый хост в файле “/etc/hosts”
    127.0.0.1     mysites

- Для активации виртуального хоста выполнил команду: “sudo a2ensite”, после ввода пароля спросит какой файл подключить.

На заметку:
Для деактивации хоста можно использовать смежную команду: sudo a2dissite
Активированные хосты можно узнать по содержимому папки “/etc/apache2/sites-enabled”

- Для вступления изменений в силу: “sudo /etc/init.d/apache2 reload”, можно и перезапустить apache: “sudo /etc/init.d/apache2 restart”
- Локальную страницу можно вызвать задав в браузере имя сервера, в примере это “mysites”

5. Описанным в 4-ом пункте образом можно создать множество виртуальных хостов, но снаружи (моя главная система, в которой запущена виртуальная машина) будет видна только дефольтная страница. Ее можно использовать для показа каталога доступных страниц, например так:

<html><body><h1>It works!</h1>
<p>This is the default web page for this server.</p>
<p>The web server software is running but no content has been added, yet.</p>
<p/>
<p><a href="/mysites/">mysites</a></p>
<p><a href="/xxx/">xxx</a></p>
</body></html>


* This source code was highlighted with Source Code Highlighter.


Все!

воскресенье, 19 февраля 2012 г.

Отменяемая асинхронная операция при помощи Task Parallel Library .NET 4

Недавно в одной WinForms программе мне понадобилось воспользоваться асинхронными операции, чтобы не блокировать графический интерфейс во время длительных расчетов. Это можно реализовать, конечно же, очень многими способами, от “ручного” программирования потоков (Threads) до использования таких конструкций как BackgroundWorker. Но мне захотелось (наконец-то!) попробовать новые библиотеки для параллельного программирования появившиеся в .NET 4 версии.

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

Вот что у меня получилось:


    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;
    using System.Threading.Tasks;

    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }

            private void startButton_Click(object sender, EventArgs e)
            {
                StopCalcIfRun();
                BeginCalc();
            }

            private void stopButton_Click(object sender, EventArgs e)
            {
                StopCalcIfRun();
            }

            // --------------------------------------

            class CalcResult
            {
                public int IntResult;
            }

            // объект задачи одновременно является флагом, нулевое значение которого указывает на то, запущена ли операция
            volatile Task calcTask = null;

            void StopCalcIfRun()
            {
                Task task = calcTask;

                if (task != null)
                {
                    // извлекаем CancellationTokenSource для отмены операции
                    CancellationTokenSource ts = (CancellationTokenSource)task.AsyncState;
                    ts.Cancel();

                    try
                    {
                        task.Wait();
                    }
                    catch (AggregateException e)
                    {
                        // Если e.InnerExceptions содержит исключение TaskCanceledException,
                        // то это указывает на "успешную" отмену задачи, а не на сбой
                    }
                }
            }

            void BeginCalc()
            {
                TaskScheduler uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

                CancellationTokenSource tockenSource = new CancellationTokenSource();
                CancellationToken tocken = tockenSource.Token;

                // Создаем и запускаем длительную операцию.
                // Вниманине, tockenSource "закрепляется" за задачей как State Object, и используется для ее отмены в StopCalcIfRun методе
                calcTask = Task.Factory.StartNew((xxx) =>
                {
                    try
                    {
                        // уже отменили задачу?
                        tocken.ThrowIfCancellationRequested(); // *** Отмена задачи ***
                        CalcResult result = new CalcResult();

                        // длительные вычисления
                        result.IntResult++;
                        System.Threading.Thread.Sleep(1000);
                     
                        if (tocken.IsCancellationRequested) { // *** Отмена задачи ***
                            // ... освобождаем ресурсы
                            tocken.ThrowIfCancellationRequested();
                        }

                        // продолжаем длительные вычисления
                        result.IntResult++;
                        System.Threading.Thread.Sleep(1000);
                     
                        if (tocken.IsCancellationRequested) { // *** Отмена задачи ***
                            // ... освобождаем ресурсы
                            tocken.ThrowIfCancellationRequested();
                        }
                     
                        return result;
                    }
                    finally
                    {
                        calcTask = null;
                    }
                }, tockenSource, tocken);

                // Это продолжение будет вызвано в случае возникновения исключения (см. TaskContinuationOptions).
                // Исключение TaskCanceledException отменяющее задачу является корректным завершением задачи
                // и к таким исключениям не относится!
                calcTask.ContinueWith((ant) =>
                {
                     MessageBox.Show(ant.Exception.InnerException.ToString());
                }
                    , System.Threading.CancellationToken.None
                    , TaskContinuationOptions.OnlyOnFaulted
                    , uiTaskScheduler);

                // Продолжение если задача отменяется.
                // ant.Result есть null, не стоит к нему обращатся
                calcTask.ContinueWith((ant) =>
                {
                    resultTextBox.Text += "Canceled;";
                }
                    , System.Threading.CancellationToken.None
                    , TaskContinuationOptions.OnlyOnCanceled
                    , uiTaskScheduler);

                // Полное завершение задачи => отображаем результаты
                calcTask.ContinueWith((ant) =>
                {
                    resultTextBox.Text += ant.Result.IntResult + ";";
                }
                    , System.Threading.CancellationToken.None
                    , TaskContinuationOptions.OnlyOnRanToCompletion
                    , uiTaskScheduler);
            }
        }
    }


Ресурсы:

Threading in C#, Joseph Albahari
http://www.albahari.com/threading/part5.aspx

Перевод части статьи Джозефа Албахари (Joseph Albahari) от Sergey Teplyakov
http://sergeyteplyakov.blogspot.com/2010/09/52.html

Все дело в SynchronizationContext [RU. MSDN Magazine]
http://msdn.microsoft.com/ru-ru/magazine/gg598924.aspx

Отмена задач [RU. MSDN Статья]
http://msdn.microsoft.com/ru-ru/library/dd997396.aspx

суббота, 28 января 2012 г.

Visual Studio 2010 Color Schema. Моя темная цветовая схема

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

Редакотор c# кода:


XAML:


XML:


Основной каркас этой схемы когда-то накопал на сайте studiostyl.es, как она называлась изначально уже и не вспомню. Постепенно, по мере надобности, оптимизировал ее до существующей. Цвета, на мой взгляд, достаточно мягкие, что бы смотреть на них долгое время, глаза не устают.

Скачать схему