13.11.2009
Re-использование кода
1. Нужен корпоративный портал, который предоставит всю функциональность в удобном виде (что-то типа code.google.com, sourceforge и т.п.)
2. Все модули, которые предполагается повторно использовать, должны версионироваться - если в каком-то проекте уже используется какая-то версия модуля, то его нельзя менять!
3. Модуль должен быть снабжен документацией в вики-формате, состоящей из двух частей - человеческой и автосгенерированной по коду.
4. Документация должна версионироваться вместе с модулем.
5. При создании новой версии модуля необходимо обязательно указывать отличия. Провели рефакторинг, ускорили алгоритм на 20% - будьте добры написать. Возможно, кто-то только этого и ждал.
6. Разработчики проекта, в котом используется такой общий модуль, автоматически подписываются (пожизненно :) на выход новых версий модуля.
7. Модули должны быть доступны как в собранном виде, так и в виде исходников, чтобы все-таки позволить сделать копи-паст.
8. И было бы неплохо иметь утилиту, которая попробует собрать все (!) проекты, использующие данный модуль, с новой версией модуля. И запустит тесты. Ибо удобство превыше всего.
Ну и должна быть страница, которая угадывает мысли. Нужен вам какой-то функционал - пожалуйста, смотрите вот этот модуль. Без этой страницы никакое повторное использование невозможно. Именно у нее будут спрашивать: "а не написал ли уже кто-нибудь то, что мне нужно?"
28.10.2009
Как определить город посетителя (определение адреса по ip)
#coding=utf-8
def getGeoData(ip):
"""Получить город по ip-адресу (адрес предается как строка).
Возвращает словарь с элементами-строками city, region, district
"""
import httplib
import re
from xml.dom.minidom import parseString
conn = httplib.HTTPConnection("194.85.91.253:8090")
conn.request("POST", "/geo/geo.html", \
"<ipquery><fields><all/></fields><ip-list><ip>" + ip + \
"</ip></ip-list></ipquery>")
resp = conn.getresponse()
data = resp.read()
conn.close()
# если ничего не найдено
if re.search("<message>Not found</message>", data):
return None
dom = parseString(data)
city = dom.documentElement.getElementsByTagName('city')[0]\
.firstChild.nodeValue
region = dom.documentElement.getElementsByTagName('region')[0]\
.firstChild.nodeValue
district = dom.documentElement.getElementsByTagName('district')[0]\
.firstChild.nodeValue
return {'city' : city, 'region' : region, 'district' : district}
Теперь вопрос остальным: кто как справляется с этой задачей?
UPD: иногда сервис неверно определяет адрес по ip, потому что некоторые провайдеры используют общий DHCP для всех регионов, или же информация об адресах еще не собрана. Войдя на IpGeoBase, можно указать свой правильный адрес, если по ip он определился неверно.
15.10.2009
Memcached для Windows и .NET
Еще хуже, если вы включаете для пула приложений режим "веб-сад", т.е. позволяете IIS запускать несколько рабочих процессов для вашего сервиса. Теперь каждый экземпляр вашего сервиса будет делать запросы в базу и складывать все в свой собственный кэш. Вот для таких случаев лучше всего подходит memcached для windows. Этот кэш будет существовать в единственном экземпляре, обслуживать сколько угодно процессов и сбрасываться только при рестарте системы.
Собственно, сам memcached тянуть отсюда (ищите бинарник под свою винду). В архиве будет один exe-шник. Его можно скопировать, например, в C:\Program files\Memcached\ (папку предварительно надо создать). Потом из командной строки запускаем memcached -d install, в результате он регистрирует себя как windows-сервис (службу) и выполняется с правами "локальная система". По умолчанию он слушает на 11211 порту. Осталось только стартануть сервис memcached из консоли управления службами. После перезагрузки он будет запускаться автоматически.
Теперь про клиентскую часть. Под .NET клиентов несколько, я себе поставил этот. Из архива нам нужны только 2 модуля: log4net.dll и собственно Enyim.Caching.dll. В проекте не забудьте указать ссылки на оба.
sample.config содержит фрагменты, которые вы должны вставить в app.config или web.config. Там может быть указано несколько серверов - вам надо оставить только один с адресом 127.0.0.1 и прописать порт 11211. Особое внимание на тэг <memcached enabled="true"> - так работать не будет. Его надо заменить просто на <memcached>.
Все, можно пользоваться. Мануалов к этому клиенту я не нашел, но он должен придерживаться общепринятого стандарта, так что пойдет любая документация по memcached-клиенту. Еще хочется отметить, что все собственные типы данных, которые вы собираетесь класть к кэш, должны быть помечены атрибутом [Serializable] или [DataContract] (это из WCF). В общем, они должны быть сериализуемыми.
Небольшой пример на C#:
// создание клиента очень быстрое,
// так что не обязательно делать его синглтоном
MemcachedClient client = new MemcachedClient();
// сохранить в кэше
client.Store(StoreMode.Set, "MyIntValue", 12345,
DateTime.Now.AddMinutes(20));
// получить из кэша
int value = client.Get<int>("MyIntValue");
// удалить из кэша
client.Remove("MyIntValue");
05.10.2009
Проблемы с easy_install MySQL-python
Кстати, 1 октября вышел новый релиз Питона - 2.6.3. Исправлено около ста багов, новых фич не добавлено.
27.07.2009
Semantic Web: вы в него верите?
Web 3.0
Я здесь буду говорить о том, как плох Semantic Web.
Что имеем сейчас
Кстати, о великих. О кое-какой (примитивной) поддержке Semantic Web заявляли в разное время Yahoo, Google и Яндекс (поддержка тега Friend-Of-A-Friend в блогах).
Красота
Придут ли все к единому стандарту? Как вариант, я рассматриваю параллельное существование слабо совместимых языков описания онтологий и механизмов их анализа. Помимо этого, поисковые системы наверняка будут строить свои всеобъемлющие онтологии (на своем языке описания :).
Еще вопросы
Позволяет ли он описывать сложные ограничения, например, что «у мужчины может быть только одна жена, если он не мусульманин; иначе до 4»?
Позволяет ли он описать процесс преобразования одного объекта в другой, чтобы система вывода смогла составить цепочку преобразований для выполнения некоторого действия? Например, у вашего друга День Рождения, и он больше всего на свете любит вареную сгущенку вперемешку с обычной. Но ни один магазин
Я пока не нашел ответов. Уважаемые читатели, если кто обладает расширенными знаниями в этой области – поделитесь, пожалуйста.
Мое ИМХО
24.07.2009
Автоматическое увеличение номера билда при автоматической сборке в MS Team Foundation Server
Класс, содержащий метод для инкрементирования одного числа, записанного в файл может выглядеть следующим образом:
public class IncrementingNumber : Task
{
public override bool Execute()
{
NextNumber = IncrementNumber();
LastNumber = NextNumber - 1;
return true;
}
public int IncrementNumber()
{
using (FileStream fs = new FileStream(NumberFile,
FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
{
StreamReader reader = new StreamReader(fs);
long pos = 0;
String line = reader.ReadLine();
while (line != null && line.StartsWith("#"))
{
pos = pos +
line.Length +
System.Environment.NewLine.Length;
line = reader.ReadLine();
}
int number = -1;
if (line != null)
number = Int32.Parse(line);
NextNumber = number + 1;
fs.Position = pos;
StreamWriter writer = new StreamWriter(fs);
writer.WriteLine(NextNumber.ToString());
writer.Flush();
writer.Close();
}
return NextNumber;
}
[Required]
public string NumberFile { get; set; }
[Output]
public int NextNumber { get; set; }
[Output]
public int LastNumber { get; set; }
}
Для включения сборки в файл проекта .proj необходимо записать следующий тэг:
<usingtask taskname="" assemblyfile=""></usingtask>
Далее, для инкрементирования и установки номера версии сборки необходимо переопределить target BuildNumberOverrideTarget примерно следующим образом:
22.07.2009
Планировщик заданий (Task Scheduler 1.0) Windows: программный доступ
Введение
schtasks
с необходимыми параметрами. Для программистов существует хорошо документированный Com-интерфейс, но в .Net нет обертки для него. Далее будет описано, как на C# организовать работу с планировщиком заданий.Классы
ScheduledTasks
Класс
ScheduledTasks
является отображением системной папки того компьютера (локального или в сети), где хранятся все файлы с заданиями.// создаем объект ScheduledTasks
для машины с именем
//"DALLAS"
ScheduledTasks st = new ScheduledTasks(@"\\DALLAS");
// получение списка имен задач
string[] taskNames = st.GetTaskNames();
Dispose()
для освобождения этого интерфейса.Task
TriggerList
, о котором будет рассказано ниже.После содания, открытия и изменения необходимо сохранить задание в его файл с помощью функции
Save()
. Функция Save(string name)
работает как и Windows-функция "Сохранить как". Данный класс не имеет public конструктора. Все процедуры по созданию, открытию выполняются с помощью класса ScheduledTasks
посредсвтом вызова его методов Open()
или Create()
.// открываем задание с именем "foo" локальной машины
ScheduledTasks st = new ScheduledTasks();
Task t = st.OpenTask("foo");
Для освобождения СОМ-ресурсов используем функцию Close().// закрываем задание для освобождения COM ресурса.
t.Close();
TriggerList
Task
.// получение триггеров локального задания "foo".
ScheduledTasks st = new ScheduledTasks();
Task t = st.OpenTask("foo");
TriggerList tl = t.Triggers;
После закрытия задания данный список становится неактуальным.Trigger
// добавление триггера для задания "foo", чтобы оно
//выполнялось в 16:30 каждый день
Trigger tg = new DailyTrigger(16, 30); // часы и минуты
ScheduledTasks st = new ScheduledTasks();
Task t = st.OpenTask("foo");
t.Triggers.Add(tg);
t.Save();
Trigger
:- RunOnceTrigger - выполнять однажды
- DailyTrigger - выполнять ежедневно
- WeeklyTrigger - выполнять еженедельно
- MonthlyDOWTrigger - выполнять ежемесячно с учетом дней в недели и самих недель
- MonthlyTrigger - выполнять ежемесячно с учетом дней с месяце
- OnIdleTrigger - выполнять при простое системы
- OnSystemStartTrigger - выполнять при старте системы
- OnLogonTrigger - выполнять при входе пользователя в систему
TriggerList
нужного задания. Освобождать его ресурсы нет необходимости, т.к. они автоматически освобождаюся при закрытии задания. Следующия правила позволят вам избежать некоторых ошибок при программировании планировщика заданий:- Конкретный экземпляр триггера может единыжды содержаться в списке триггеров задания. Невозможно добавить триггер в список, если он там уже присутствует (т.е. в списке должна быть только одна ссылка на этот объект). Для создания такого же триггера в другом списке необходимо воспользоваться функцией
Clone()
для создания непривязанного триггера и только потом добавить его в новый список. - Привязанный триггер невозможно использовать, если родительское задание закрыто.
Примеры использования
// получение ScheduledTasks для компьютера "DALLAS"
ScheduledTasks st = new ScheduledTasks(@"\\DALLAS");
// полученеи списка имен заданий
string[] taskNames = st.GetTaskNames();
// получение каждого задания и вывод его описания
foreach (string name in taskNames) {
Task t = st.OpenTask(name);
Console.WriteLine(" " + t.ToString());
t.Close();
}
// освобождаем COM - ресурсы.
st.Dispose();
2. Создание нового задания
//получение ScheduledTasks локального компьютера.
ScheduledTasks st = new ScheduledTasks();
// Создание задания с именем "D checker"
Task t;
try
{
t = st.CreateTask("D checker");
}
catch (ArgumentException)
{
Console.WriteLine("Задание с таким именем уже
существует");
return;
}
// Заполнение информации о программе для запуска
t.ApplicationName = "chkdsk.exe";
t.Parameters = "d: /f";
t.Comment = "Проверка диска D:";
// установка аккаунта, под которым будет
//происходить выполнение.
t.SetAccountInformation(@"THEDOMAIN\TheUser",
"HisPasswd");
// Говорим системе ожидать 10 мин перед запуском
//программы
t.IdleWaitMinutes = 10;
// позволяем заданию выполняться не более 2 часов,
//30 минут.
t.MaxRunTime = new TimeSpan(2, 30, 0);
// говорим системе запускать задание только при
//простое.
t.Priority = System.Diagnostics.ProcessPriorityClass.Idle;
// создаем триггер для запуска каждое воскресенье
//в 6:30.
t.Triggers.Add(new WeeklyTrigger(6, 30,
DaysOfTheWeek.Sunday));
// Сохраняемся
t.Save();
// освобождаем все ресурсы.
t.Close();
// освобождаем ресурсы.
st.Dispose();
3. Меняем время выполнения задания
//получение ScheduledTasks локального компьютера.
ScheduledTasks st = new ScheduledTasks();
// открываем задание, которое было добавлено
Task task = st.OpenTask("D checker");
// удостоверяемся, что оно есть
if (task != null) {
// пробегаем по всем триггерам
foreach (Trigger tr in task.Triggers)
{
// если триггер имеет время начала выполнения задания,
// меняем на 4:15.
if (tr is StartableTrigger)
{
(tr as StartableTrigger).StartHour = 4;
(tr as StartableTrigger).StartMinute = 15;
}
}
task.Save();
task.Close();
}
st.Dispose();
http://www.codeproject.com/KB/cs/tsnewlib/TSNewLib_src.zip
из статьи:
http://www.codeproject.com/KB/cs/tsnewlib.aspx
GWT и Maven
Cоздаем maven проект.
В качестве типа проекта выбираем, что проект будет просто (не использовать никаких архетипов).
Конфигурируем проект
После того, как проект создан, нобходимо настроить зависимости gwt. Для этого можно воспользоваться инструкцией, которую написали разработчики gwt. Полученный проект выглядит так:
Так как gwt это веб-приложение, то необходимо создать в проекте папку war, в ней WEB_INF и добавить файл настройки веб-приложения web.xml. Так же добавить в WEB_INF папку lib для хранения библиотек необходимых для работы gwt.
Для того чтобы скомпилировать gwt проект можно воспользоваться build.xml, который генерируется при создании проекта для eclipse. Его необходимо отредактировать, чтобы он понимал структуру проекта maven
<project name="org.test.gwtMaven"Необходимо добавить plugin к Ant для того чтобы он смог интегрироваться с maven. Подключаем plugin для данной сборки xmlns:artifact="antlib:org.apache.maven.artifact.ant">.
....default="build"
....basedir="."
....xmlns:artifact="antlib:org.apache.maven.artifact.ant">
....
</project>
Добавляем свойства, которые можно будет использовать в тасках Ant:
<property name="gwt.version" value="1.7.0" />Создаем список файлов, который будет получен из pom для использования при сборки.
<property name="gwt.sdk" location="war/WEB-INF/gwt-sdk" />
<property name="platform" value="windows" />
<artifact:dependencies filesetId="dependency.fileset">Настройка classpath:
....<pom file="pom.xml" />
</artifact:dependencies>
<path id="project.class.path">Загруза библиотек необходимых для компиляции и работы GWT.
....<pathelement location="war/WEB-INF/classes" />
....<pathelement location="${gwt.sdk}/gwt-user-${gwt.version}.jar" />
....<fileset dir="${gwt.sdk}" includes="gwt-dev*.jar" />
....<fileset dir="${gwt.sdk}" includes="*.jar" />
....<fileset dir="war/WEB-INF/lib" includes="**/*.jar" />
</path>
<target name="libs" description="">Компиляция исходных кодов.
....<mkdir dir="war/WEB-INF/gwt-sdk" />
....<copy todir="war/WEB-INF/gwt-sdk">
........<fileset refid="dependency.fileset" />
........<mapper type="flatten" />
....</copy>
....<unzip src="${gwt.sdk}/gwt-dev-${gwt.version}-${platform}-libs.zip"
............dest="${gwt.sdk}/" />
....<copy todir="war/WEB-INF/lib"
............file="${gwt.sdk}/gwt-servlet-${gwt.version}.jar" />
</target>
<target name="javac" depends="libs" description="">Компиляция Java в JavaScript.
....<mkdir dir="war/WEB-INF/classes" />
....<javac srcdir="src/main/java"
.... ....includes="**"
.... ....encoding="utf-8"
........destdir="war/WEB-INF/classes"
........source="1.5" target="1.5"
........nowarn="true"
........debug="true"
........debuglevel="lines,vars,source">
<classpath refid="project.class.path" />
....</javac>
....<copy todir="war/WEB-INF/classes">
........<fileset dir="src/main/java" excludes="**/*.java" />
....</copy>
</target>
<target name="gwtc" depends="javac" description="">Сборка всего проекта GWT.
....<java failonerror="true" fork="true" classname="com.google.gwt.dev.Compiler">
........<classpath>
................<pathelement location="src/main/java" />
................<path refid="project.class.path" />
........</classpath>
........<jvmarg value="-Xmx256M" />
........<arg value="org.test.MainEntryPoint" />
....</java>
</target>
<target name="build" depends="gwtc" description="" />Упаковка проект в war файл для размещения на веб-сервере.
<target name="war" depends="build" description="">
....<zip destfile="org.test.gwtMaven.war" basedir="war" />
</target>
<target name="clean" description="">На этом редактирование build.xml пока закончим. Теперь необходимо создать класс-точку входа в gwt приложения. Для этого создадим два namespace'a. org.test и org.test.gwtMavenClient в папке исходников src/main/java. В org.test.gwtMavenClient создадим класс MainEntryPoint, который реализует интерфейс com.google.gwt.core.client.EntryPoint.
....<delete dir="war/WEB-INF/classes" failonerror="false" />
....<delete dir="war/gwt" failonerror="false" />
....<delete dir="war/WEB-INF/gwt-sdk" failonerror="false" />
....<delete dir="war/gwtMain" />
....<delete file="war/WEB-INF/lib/gwt-servlet-${gwt.version}.jar" />
</target>
В org.test создим MainEntryPoint.gwt.xml, который будет содержать настройки gwt-приложения.
package org.test.gwtMain;
import com.google.gwt.core.client.EntryPoint;
public class MainEntryPoint implements EntryPoint{
public void onModuleLoad() {
// TODO Auto-generated method stub
}
}
Дерево проекта выглядет вот так.
<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='gwtMain'>
<inherits name='com.google.gwt.user.User' />
<inherits name='com.google.gwt.i18n.I18N' />
<extend-property name="locale" values="ru_RU" />
<inherits name='com.google.gwt.user.theme.chrome.Chrome' />
<source path="gwtMain" />
<entry-point class='org.test.gwtMain.MainEntryPoint' />
</module>
Gwt-приложению необходимо где-то хоститься. Для этого в папке war необходимо создать index.html.
<html>А в build.xml добавить target, который позволит запускать приложения в специальном отладочном браузере
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="content-language" content="ru">
<title>Insert title here</title>
<script type="text/javascript" language="javascript"
src="gwtMain/gwtMain.nocache.js"></script>
</head>
<body>
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1'
style="position: absolute; width: 0; height: 0; border: 0"></iframe>
<div id="mainWorkSpace"></div>
</body>
</html>
<target name="hosted"Каркас готов. Продолжение следует...
....depends="javac" description="">
........<java
...............failonerror="true"
...............fork="true"
...............classname="com.google.gwt.dev.HostedMode">
............<classpath>
................<pathelement location="src/main/java" />
................<path refid="project.class.path" />
............</classpath>
............<jvmarg value="-Xmx256M" />
............<arg value="-startupUrl" />
............<arg value="index.html" />
............<arg value="org.test.MainEntryPoint" />
........</java>
</target>
Свой jdbc драйвер и Kettle (Pentaho Data Integration)
static {
....try {
........DriverManager.registerDriver(new ParadoxDriver());
} catch (SQLException e) {
...
....}
}
Создание локального Maven репозитория
В качестве веб-сервера берем Apache, устанавливаем его и настраиваем конфигурационные файлы следующим образом:
в hhtpd.conf
...в папке conf/extra/ создаем файл httpd-maven2repo.conf и заносим следующую информацию:
Include conf/extra/httpd-maven2repo.conf
...
<путь_к_репо> - адрес папки в файловой системе, где будет репозиторий.
Alias /maven2repo/ "<путь_к_репо>"
Options Indexes FollowSymLinks MultiViews
Order allow,deny
Allow from all
Репозиторий готов.
Для того чтобы поместить готовый package в репозиторий, необходимо скачать maven, скопировать его на сервер, где будет распалагаться хранилище и настроить его. Для этого необходимо отредактировать файл /conf/settings.xml в папке maven'a
<localrepository><путь_к_репо></localrepository>Теперь при запуске на сервере mvn install пакеты будут помещаться в наш репозиторий. А клиенты смогут получать эти пакеты через maven, указывая в качестве репозитория наш сервер.
11.06.2009
Особенности создания ярлыков при инсталляции
error LGHT0204: ICE43: Component- в случае, если мы для размещения ярлыка используем атрибут Directory.has non-advertised shortcuts. It should use a registry key under HKCU as its KeyPath, not a file.
error LGHT0204: ICE57: Componenthas both per-user and per-machine data with a per-machine KeyPath.
error LGHT0204: ICE18: KeyPath for Component:is Directory: 'DesktopFolder'. The Directory/Component pair must be listed in the CreateFolders table.
error LGHT0204: ICE38: Componentinstalls to user profile. It must use a registry key under HKCU as its KeyPath, not a file.
error LGHT0204: ICE43: Componenthas non-advertised shortcuts. It should use a registry key under HKCU as its KeyPath, not a file.
02.04.2009
ChildData in Parallel Replicator
Для решения данной проблемы можно поступить следующим образом:
-определить свой SequenceActivity, содержащий ссылку на данные для экземпляра репликатора. (поле _instanceData)
[Serializable]
public class StoreSequence : SequenceActivity
{
--private object _instanceData = null;
--public object InstanceData
--{
------get { return _instanceData; }
------set { _instanceData = value; }
--}
}
-поместить данный Activity в качестве дочернего элемента в Replicator Activity, а реплицируемые элементы на StoreSequence .
-при выполнение метода ChildInitialized (инициализация дочерних activity) сохранить данные в созданный контейнер
...
private void sendAlertsReplicator_ChildInitialized(object sender, ReplicatorChildEventArgs e)
{
----((StoreSequence)e.Activity).InstanceData = e.InstanceData;
}
...
31.03.2009
Бинарнарный сериализатор. (BinarySerializer)
[<element>, <element>] - поля класса
где <element> - это простой тип данных:
<size> - размер тела (4 байта)
- простой тип переведенный в байты
или массив представление сериализованного массива аналогично классу (см. выше)
Данный сериализатор поддерживает следующие простые типы:
bool, short, int, long(Int64), float(Single), double, Guid.
Механизм содержит три основные части:
1. Атрибуты, которые указывают, что класс или поле необходимо сериализовать.
2. Временный контейнер, который используется для хранения данных при сериализации.
3. Собственно сам механизм сериализации.
Атрибуты:
// Атрибут для класса. Используется в BinarySerializer
[AttributeUsage(
AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class ClassToBinaryAttribute : Attribute
{
}
/// Атрибут для поля класса. Используется в BinarySerializer
[AttributeUsage(
AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class FieldToBinaryAttribute : Attribute
{
}
Временный контейнер:
/// <summary>
/// Контейнер для хранение сериализованных бинарных данных
/// </summary>
public abstract class BinaryContainer
{
protected List<BinaryContainer> _childs =
new List<BinaryContainer>();
/// <summary>
/// Дочерние элементы контейнера
/// </summary>
public List<BinaryContainer> Childs
{
get { return this._childs; }
}
protected byte[] _body;
/// <summary>
/// Сериализованные данные
/// </summary>
public byte[] Body
{
//get { return _body; }
set { _body = value; }
}
/// <summary>
/// Получить размер бинарной информации
/// </summary>
/// <returns></returns>
public abstract int GetBodySize();
/// <summary>
/// Получить бинарные данные
/// </summary>
/// <returns></returns>
public abstract byte[] GetBody();
}
/// <summary>
/// Контейнер для поля
/// </summary>
class BinaryField : BinaryContainer
{
public override int GetBodySize()
{
if (this._body != null)
return this._body.Length + 4;
else
return 0;
}
public override byte[] GetBody()
{
if (this._body != null)
{
// весь размер буфера: размер + данные
int fullSize = this._body.Length + 4;
byte[] newBody = new byte[fullSize];
Buffer.BlockCopy(
BitConverter.GetBytes(this._body.Length), 0, newBody, 0, 4);
Buffer.BlockCopy(
this._body, 0, newBody, 4, this._body.Length);
return newBody;
}
else
return null;
}
}
/// <summary>
/// Контейнер для класса или массива
/// </summary>
class BinaryArray : BinaryContainer
{
public int ArrayElementCount
{
get { return this._childs.Count; }
}
public override int GetBodySize()
{
int size = 4;
if (this._childs.Count > 0)
{
foreach (BinaryContainer child in this._childs)
size += child.GetBodySize();
return size;
}
else
return size;
}
public override byte[] GetBody()
{
if (this._childs.Count > 0)
{
int bodySize = this.GetBodySize();
this._body = new byte[bodySize];
Buffer.BlockCopy(
BitConverter.GetBytes(
this.ArrayElementCount), 0, this._body, 0, 4);
int offset = 4;
foreach (BinaryContainer child in this._childs)
{
int childSize = child.GetBodySize();
byte[] childBody = child.GetBody();
Buffer.BlockCopy(
childBody, 0, this._body, offset, childSize);
offset += childSize;
}
return this._body;
}
else
return new byte[4];
}
}
-тип для временного хранения простых типов (BinaryArray);
-тип для временного хранения сложных типов, таких как классы и массивы (BinaryField).
Два метода GetBodySize и GetBody предоставляют возможность для получения размера тела и сам байтовый массив. Паттерн Dependency Injection позволяет прозрачно, для основного механизма сериализации, использовать типы контейнера.
Сериализатор:
/// <summary>
/// Бинарный сериализатор
/// Поддержка следующих типов данных:
/// классы, массивы,
/// bool, short, int32, long(int64), float(single), double, Guid
/// Сереализация generic типов не поддерживается
/// </summary>
public class BinarySerializer
{
/// <summary>
/// Сериализация объекта
/// </summary>
/// <typeparam name="TType">Тип объекта</typeparam>
/// <param name="obj">Объект</param>
/// <returns>Массив байт</returns>
public byte[] Serialize<TType>(TType obj)
{
BinaryContainer binaryContainer = this.ClassSerializer(typeof(TType), obj);
byte[] result = binaryContainer.GetBody();
return result;
}
#region Хелперы для сериализации
/// <summary>
/// Сериализация объекта
/// </summary>
/// <param name="type">Тип объекта</param>
/// <param name="obj">Объект</param>
/// <returns>Временный контейнер</returns>
private BinaryContainer ClassSerializer(Type type, object obj)
{
// получение полей в типе
FieldInfo[] fInfos = type.GetFields(
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public);
BinaryContainer classContainer = new BinaryArray();
foreach (FieldInfo fInfo in fInfos)
{
object[] customFieldAttributes =
fInfo.GetCustomAttributes(
ypeof(FieldToBinaryAttribute), false);
if (customFieldAttributes.Length == 1)
{
// получение аттрибута у типа поля
object[] customTypeAttributes =
fInfo.FieldType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);
if (customTypeAttributes.Length == 1) // поле сложного типа
{
classContainer.Childs.Add(
this.ClassSerializer(fInfo.FieldType, fInfo.GetValue(obj)));
}
else // поле простого типа
{
if (fInfo.FieldType.IsArray) // поле массив
{
//сериализация массива
classContainer.Childs.Add(
this.ArraySerializer(fInfo, obj));
}
else // поле простого типа
classContainer.Childs.Add(
this.SimpleTypeSerializer(
fInfo.FieldType, fInfo.GetValue(obj)));
}
}
}
return classContainer;
}
/// <summary>
/// Сериализация массива
/// </summary>
/// <param name="fInfo">Дескриптор поля</param>
/// <param name="obj">Объект</param>
/// <returns>Бинарный контейнер с сереализованным массивом</returns>
private BinaryContainer ArraySerializer(FieldInfo fInfo, object obj)
{
// тип элемента массива
Type elementType = fInfo.FieldType.GetElementType();
Array array = (Array)fInfo.GetValue(obj);
bool isClass = false;
// получение аттрибута у типа поля
object[] customTypeAttributes =
elementType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);
if (customTypeAttributes.Length == 1)
isClass = true;
// контейнер под хранения массива в бинарном виде
BinaryContainer binaryArray = new BinaryArray();
// испульзуем энумератор для передвижения по массиву
IEnumerator arrayEnumerator = array.GetEnumerator();
while (arrayEnumerator.MoveNext() &&
arrayEnumerator.Current != null)
{
BinaryContainer arrayElement = null;
if (isClass)
{
arrayElement =
this.ClassSerializer(
elementType, arrayEnumerator.Current);
}
else
{
// получение бинарного представления для элемента массива
arrayElement =
this.SimpleTypeSerializer(
elementType, arrayEnumerator.Current);
}
// добавление простого типа к бинарному контейнеру
binaryArray.Childs.Add(arrayElement);
}
return binaryArray;
}
/// <summary>
/// Сериализация простых типов
/// bool, short, int32, long(int64), float(single), double, Guid
/// </summary>
/// <param name="fieldType">Тип поля</param>
/// <param name="value">Значение поля</param>
/// <returns>Временный контейнер</returns>
private BinaryContainer SimpleTypeSerializer(Type fieldType, object value)
{
// буфер для хранения сериализовнных данных
byte[] buffer = null;
if (fieldType == typeof(Boolean))
{
buffer = BitConverter.GetBytes((Boolean)value);
}
else if (fieldType == typeof(Int16))
{
buffer = BitConverter.GetBytes((Int16)value);
}
else if (fieldType == typeof(Int32))
{
buffer = BitConverter.GetBytes((Int32)value);
}
else if (fieldType == typeof(Int64))
{
buffer = BitConverter.GetBytes((Int64)value);
}
else if (fieldType == typeof(Single))
{
buffer = BitConverter.GetBytes((Single)value);
}
else if (fieldType == typeof(Double))
{
buffer = BitConverter.GetBytes((Double)value);
}
else if (fieldType == typeof(Guid))
{
buffer = ((Guid)value).ToByteArray();
}
BinaryContainer tempC = null;
if (buffer != null && buffer.Length > 0)
{
tempC = new BinaryField();
tempC.Body = buffer;
}
else
throw new Exception("Ошибка в сериализациия поля типа " + fieldType.Name);
return tempC;
}
#endregion
/// <summary>
/// Десерилазиация
/// </summary>
/// <typeparam name="TType">Тип объекта</typeparam>
/// <param name="byteObj">Массив байт</param>
/// <returns>Объект заданного типа</returns>
public TType Deserialize<TType>(byte[] byteObj)
{
int offset = 0;
TType inst =
(TType)this.ClassDesirealize(
typeof(TType), byteObj, ref offset);
return inst;
}
#region Хелперы для десериализации
/// <summary>
/// Десериализация класса
/// </summary>
/// <param name="type">Тип класса</param>
/// <param name="data">Бинарные данные</param>
/// <returns>Экземпляр объекта</returns>
private object ClassDesirealize(
Type type, byte[] data, ref int offset)
{
// получение полей в типе
FieldInfo[] fInfos = type.GetFields(
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public);
//создание объекта типа type
object obj = this.CreateInstance(type);
offset += 4; // первые 4 байта класса - количество полей
foreach (FieldInfo fInfo in fInfos)
{
object[] customFieldAttributes =
fInfo.GetCustomAttributes(
typeof(FieldToBinaryAttribute), false);
if (customFieldAttributes.Length == 1)
{
// получение аттрибута у типа поля
object[] customTypeAttributes =
fInfo.FieldType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);
object fieldValue = null;
if (customTypeAttributes.Length == 1) // поле сложного типа
{
fieldValue =
this.ClassDesirealize(
fInfo.FieldType, data, ref offset);
}
else
{
if (fInfo.FieldType.IsArray) // поле массив
{
fieldValue =
this.ArrayDeserializer(fInfo, data, ref offset);
}
else // поле простого типа
{
// массив байт для хранения размера
byte[] sizeByte = new byte[4];
Buffer.BlockCopy(
data, offset, sizeByte, 0, sizeByte.Length);
// получение размера
int size = BitConverter.ToInt32(sizeByte, 0);
// получение данных
byte[] dataBuffer = new byte[size];
Buffer.BlockCopy(
data, offset + sizeByte.Length,
dataBuffer, 0, dataBuffer.Length);
// десериализация поля
fieldValue =
this.SimpleTypeDeserializer(
fInfo.FieldType, dataBuffer);
// увелечени сдвига по буферу
offset += sizeByte.Length + size;
}
}
// помещение значение в объект
fInfo.SetValue(obj, fieldValue);
}
}
return obj;
}
/// <summary>
/// Десериализация массива
/// </summary>
/// <param name="fInfo">Дескриптор поля</param>
/// <param name="data">Массив байт</param>
/// <param name="offset">Смещение в массиве байт</param>
/// <returns>Массив</returns>
private object ArrayDeserializer(
FieldInfo fInfo, byte[] data, ref int offset)
{
// получение количества элементов в массиве
byte[] elCountByte = new byte[4];
Buffer.BlockCopy(data, offset, elCountByte, 0, elCountByte.Length);
int elementsCount = BitConverter.ToInt32(elCountByte, 0);
// увелечение счетчика движения по буферу
offset += elCountByte.Length;
bool isClass = false;
// тип элемента массива
Type elementType = fInfo.FieldType.GetElementType();
// получение аттрибута у типа поля
object[] customTypeAttributes =
elementType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);
if (customTypeAttributes.Length == 1)
isClass = true;
// создание экземпляра массива
Array array = Array.CreateInstance(elementType, elementsCount);
//получение элементов массива
for (int i = 0; i < elementsCount; i++)
{
object value = null;
if (isClass)
{
value =
this.ClassDesirealize(
elementType, data, ref offset);
}
else
{
if (elementType.IsArray)
{
throw new NotImplementedException(
"Ошибка при десериализации в массива ");
}
else
{
// получение размера элемента массива
byte[] elSizeByte = new byte[4];
Buffer.BlockCopy(
data, offset, elSizeByte, 0, elSizeByte.Length);
int elementSize = BitConverter.ToInt32(elSizeByte, 0);
offset += elSizeByte.Length;
//получение значения элемента массива
byte[] valueByte = new byte[elementSize];
Buffer.BlockCopy(
data, offset, valueByte, 0, valueByte.Length);
value = this.SimpleTypeDeserializer(elementType, valueByte);
offset += valueByte.Length;
}
}
// заполнение массива
array.SetValue(value, i);
}
return array;
}
/// <summary>
/// Создание экземпляра класса по его типу
/// </summary>
/// <param name="type">Тип</param>
/// <returns>Объект</returns>
private object CreateInstance(Type type)
{
ConstructorInfo constructor =
type.GetConstructor(new Type[] { });
object inst = null;
if (constructor != null)
{
inst = constructor.Invoke(new object[] { });
}
else
throw new Exception(
"Не возможно создать объект типа " +
type.Name +
". Объект должен иметь пустой конструктор");
return inst;
}
/// <summary>
/// Десериализация простых типов
/// bool, short, int32, long(int64), float(single), double, Guid
/// </summary>
/// <param name="fieldType"></param>
/// <param name="data"></param>
/// <returns></returns>
public object SimpleTypeDeserializer(Type fieldType, byte[] data)
{
object value = null;
if (fieldType == typeof(Boolean))
{
value = BitConverter.ToBoolean(data, 0);
}
else if (fieldType == typeof(Int16))
{
value = BitConverter.ToInt16(data, 0);
}
else if (fieldType == typeof(Int32))
{
value = BitConverter.ToInt32(data, 0);
}
else if (fieldType == typeof(Int64))
{
value = BitConverter.ToInt64(data, 0);
}
else if (fieldType == typeof(Single))
{
value = BitConverter.ToSingle(data, 0);
}
else if (fieldType == typeof(Double))
{
value = BitConverter.ToDouble(data, 0);
}
else if (fieldType == typeof(Guid))
{
value = new Guid(data);
}
if (value == null)
throw new Exception(
"Ошибка в десериализациия поля типа " +
fieldType.Name);
return value;
}
#endregion
}
30.03.2009
Каждому экшену - свое место
Итак, определившись с типом экшена, который мы хотим включить в инсталляцию, необходимо определиться с тем местом инсталляции, в котором его необходимо выполнить. В msi существует две таблицы в которые можно размещать экшены - InstallUISequence и InstallExecuteSequence. Действия, определенные в таблице InstallUISequence выполняются в самом начале установки и как правило именно в этой таблице описывается последовательность пользовательских диалогов, которая будет выводиться для сбора сведений об инсталляции от пользователя. При размещении своих экшенов в этой таблице надо помнить об одной важной вещи - существует "тихий" режим инсталляции, в котором таблица InstallUISequence не обрабатывается. Поэтому если экшен должен выполниться в любом случае, то логично разместить его в таблице InstallExecuteSequence. Действия в таблице InstallExecuteSequence по сути отвечают за сам процесс установки, именно они копируют файлы, ведут записи в реестре и т.д. И как правило именно при их выполнении пользователь видит перед собой диалоговое окно с прогресс-баром. Но при размещении в этой таблице своих экшенов надо помнить об опциях выполнения, которые в WIX определяются атрибутом "Execute" тега "CustomAction". Важнейшими из них являются deferred и immediate. Дело в том, что установка продукта не происходит "в один проход". На определенном этапе, а если точнее, то между действиями InstallInitialize и InstallFinalize происходит запись скрипта инсталляции и те экшены, которые помечены как deferred не выполняются сразу, а записываются в скрипт и исполняются после InstallFinalize, в то время как immediate-экшены выполняются всегда в тот момент, когда приходит их очередь по таблице. Отсюда следуют несколько интересных выводов: во-первых, deferred-экшен не может быть поставлен никуда кроме как между InstallInitialize и InstallFinalize. Во-вторых, если поставить immediate-экшен и deferred-экшен в одно место после копирования файлов, то на момент выполнения immediate-действия файлы еще скопированы не будут, потому как копируются во время исполнения скрипта, а не во время его записи, соттветственно deferred-экшен сможет использовать эти файлы. То же самое относится и к другим изменениям, выполняющимся в deferred-фазу.
Также важным моментом является использование rollback-экшенов. Эти экшены создаются с использованием значения "rollback" для атрибута "Execute". Такие экшены выполняются в том случае, если во время инсталляции произошла ошибка и производится откат. Использовать их следует в паре с написанными кастом-экшенами для отката изменений, ибо сам инсталлер не откатывает то, что делают кастом-экшены.
Порядок выполнения экшенов в таблицах задается целыми числами. В WIX эти числа можно определять как напрямую через атрибут "Sequence" тега "Custom", либо через атрибуты "Before" и "After", задавая перед или после какого экшена выполняться текущему действию.