среда, 30 декабря 2009 г.

ProFTPD: Показать скрытые файлы

Сегодня встала задача - необходимо, чтобы при доступе по FTP юзеру отображались также и скрытые файлы (начинающиеся с символа "."), в частности .htaccess и ему подобные. У меня на сервере стоит ProFTPD, его настройка оказалась просто элементарной, для решения этой задачи. В конфиг добавляется срока:
ListOptions "-a"
Если уже имеется директива ListOptions в неё просто добавляется ключ "-a", например:
ListOptions "-al"
Все. Работает.

воскресенье, 27 декабря 2009 г.

Введение в Mercurial. Часть внеочередная. Конвертируемся из Subversion

Всем привет!
Давненько я ничего не писал, но для того была достойная причина - 22 декабря я все таки защитил кандидатскую, что собственно и забирало уйму времени последние месяцы. Теперь я полноправный к.т.н.

Сегодня маленький пост о том как сконвертировать имеющийся репозиторий Subversion в репозиторий Mercurial. Думаю что в необходимости перехода на Mercurial я постепенно смогу убедить своих читателей. Итак исходная позиция:
1. Имеется репозиторий svn лежащий на диске, пусть здесь: /[svn_repos_path]/svnrepo;
2. Хочется заиметь репозиторий Mercurial /[hg_repos_path/hgrepo со всей историей накопленной в Subversion, фактически импортировать все ревизии из svn в Mercurial.
Нет ничего проще.
1. Создаем новый репозиторий Mercurial:

mkdir /[hg_repos_path/hgrepo
cd /[hg_repos_path/hgrepo
hg  init


2. Разрешаем расширение convert. В Debian это делается так: в файл /[hg_repos_path/hgrepo/.hg/hgrc добавляем строчки:

[extensions]
convert=

3. А теперь выполняем собственно конвертирование:

cd /[hg_repos_path/hgrepo
hg convert file:///[svn_repos_path]/svnrepo .

Все. Mercurial выдает последовательность ревизий, и загоняет все что было в SVN репозитории в новый репозиторий Mercurial. Можно пользоваться ;)
Progg it

пятница, 27 ноября 2009 г.

Введение в Mercurial. Часть 2. Основы работы

Продолжаю писать и постить цикл статей про распределенную систему контроля версий Mercurial. Первая часть находится здесь. В этой части рассказа о распределенной системе контроля версий Mercurial речь пойдёт об основных командах, используемых при работе с репозиториями. Статья покрывает начальный уровень взаимодействия с Mercurial, и подразумевает, что у читающего есть некоторые навыки взаимодействия с централизованными системами контроля версий, например, Subversion. Все примеры в статье относятся к работе с Mercurial в unix-подобных системах, при этом для работы в Windows потребуется лишь минимальная адаптация.

Основные сведения о ревизиях в Mercurial
Как и в большистве существующих систем контроля версий, центральным понятием Mercurial являетя ревизия, которая здесь называется changeset. В связи со спецификой распределенных систем контроля версий невозможно выдать каждой ревизии её номер, поскольку не получится гарантировать его уникальность среди всех существующих репозиториев. Однако  каждая ревизия все таки имеет уникальный идентификатор, в случае Mercurial это 40-значный sha1-хеш, который учитывает все параметры ревизии. Таким образом, у каждой новой ревизии в любом удаленном репозитории будет свой уникальный идентификатор. Использование подобной нумерации ревизий немного пугает начинающих пользователей, особенно переходящих на Mercurial с svn, однако ничего страшного в них нет, и использование тех или иных идентификаторов это просто дело привычки.

Начало работы
Вся работа с системой котроля версий Mercurial происходит с помощью команды hg, и во всех постах посвященных работе непосредственно с Mercurial, я буду приводить именно консольные команды, и консольные способы работы. Безусловно есть и вполне нормальные GUI-клиенты, однако освоение лучше начинать именно с консоли, чтобы лучше понять как именно все работает, и каковы логические и алгоритмические основы взаимодействия с этой системой контроля версий.
Работа с этой системой контроля версий, как, впрочем, и со всеми остальными, начинается с создания репозитория в пустом каталоге файловой системы. Для этого перейдём в выбранный каталог, пусть это будет ~/repos/hgproject, и скажем:
> hg init
По команде «hg init» Mercurial создает репозиторий в текущем каталоге. Если посмотреть на результат работы — мы увидим  каталог «.hg», в которой собственно и хранится вся история работы над проектом.
В принципе, рабочую копию можно хранить в той же папке, где был создан репозиторий, но поскольку мы собираемся знакомится с системой контроля версий, причем с распределенной, то будет лучше создать некое подобие обычной структуры работы над проектом. Для этого создадим каталог, в котором будет располагаться наш проект и перейдем в него, пусть это будет ~/projects.
Теперь нужно получить данные для начала работы над проектом. В общем случае это будет все содержимое некоторого репозитория расположенного где-то на сервере. Для этого перейдем в ~/projects и скомандуем:
> hg clone ~/repos/hgproject
По команде «hg clone» Mercurial «клонирует» репозиторий расположенный по указанному адресу в текущий каталог. При этом к вам попадает именно репозиторий, то есть хранилище, содержащее всю существующую историю изменений, что сильно отличает операцию клонирования от того же checkout в Subversion. Таким образом, у нас уже имеется два репозитория — то есть мы локально получили именно распределенную систему контроля версий. Взаимодействие может происходить с любым имеющимся репозиторием, так как они все равноценны, однако мы назовем репозиторий в каталоге ~/repos/hgproject "центральным", то есть введем конвенцию на взаимодействие с системой. Практически в любом случае работы в команде без подобных конвенций не обойтись.

Работа с локальным репозиторием
Начнем взаимодейтсвовать с полученной структурой репозиториев. С помощью вашего любимого текстового редактора создадим новый файл в каталоге с проектом, пусть для примера это будет readme.txt, и напишем некую строку символов в этот файл. Итак мы уже получили файлы в проекте, которые необходимо хранить в репозитории. Перед тем, как сохранить новый файл в репозитории сначала убедимся в том, что Mercurial его видит, для этого в каталоге с новым файлом выполним:
>hg status
? readme.txt
Mercurial ответил, что он видит файл readme.txt, при этом этот файл пока не находится в системе контроля версий (символ «?» слева от имени файла). По команде status Mercurial выводит состояние рабочей копии в сравнении с состоянием локального репозитория. Для того, чтобы сказать Mercurial, что его необходимо версионировать скажем:
> hg add
adding readme.txt
И ещё раз:
> hg status
A readme.txt
Видим, что слева от имени файла появился символ «А», который означает что файл readme.txt будет добавлен в систему контроля версий при следующем коммите, который мы сейчас и сделаем:
>hg commit
Mercurial запустит текстовый редактор и попросит ввести описание к выполняемому коммиту. Как только вы закроете его все изменения в рабочей копии будут сохранены в локальном репозитории. Убедиться в этом достаточно просто:
>hg log
changeset:   0:8fae369766e9
tag:         tip
user:        mike@mike-notebook
date:        Fri Nov 27 08:58:01 2009 +0300
summary:     Файл readme.txt добавлен в репозиторий
Разберем, что Mercurial выдал в этом сообщении. Changeset — это и есть номер ревизии, который состоит из двух частей: виртуального номера ревизии(записан до «:») и идентификатора (sha1-хеша). Виртуальный номер ревизии призван облегчить жизнь пользователям, и все-таки ввести в эту систему некоторую нумерацию ревизий. Но, как показывает практика использовать этот номер для однозначной идентификации нельзя, так как может привести к путанице в понимании происходящего в репозиториях. Обычно для однозначной идентификации версии достаточно 4-5 шестнадцатеричных цифр идентификатора. Следующей строкой идёт «tag: tip», вообще говоря tip — это обозначение последней ревизии, хотя выбирается это обозначение в различных случаях по различным принципам, в дальнейшем, когда будем рассматривать организацию ветвлений этот момент исследуем более подробно. Значение следующих строк очевидно, и нет необходимости их как-либо комментировать.

Обмен с удаленными репозиториями
А теперь вспомним, что у нас есть ещё некий «центральный» репозиторий, через который, по идее будет происходить взаимодействие с другими членами команды разработки. При этом выполненный коммит был локальным, то есть история изменений была сохранена только в вашем локальном репозитории. Для того, чтобы передать изменения в репозиторий расположенный в ~/repos/hgproject выполним:
> hg push
pushing to ~/repos/hgproject
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
После выполнения этой команды все изменения зафиксированные в локальном репозитории были зафиксированы также и в удаленном. Теперь склонируем репозиторий ещё раз, и посмотрим как происходит обмен ревизиями в Mercurial. Создадим новый каталог ~/projects/hgproj_clone, и склонируем в него наш удаленный репозиторий:
>hg clone ~/repos/hgproject ~/projects/hgproj_clone
updating working directory
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
И уже во вновь склонированном репозитории создадим файл other.txt с помощью вашего любимого текстового редактора.  И снова повторим операции описанные выше:
> hg status
? other.txt
> hg add
adding other.txt
> hg commit
> hg log
changeset:   1:270e49e72f4b
tag:         tip
user:        mike@mike-notebook
date:        Fri Nov 27 10:39:35 2009 +0300
summary:     Записан файл other.txt в другом репозитории


changeset:   0:8fae369766e9
user:        mike@mike-notebook
date:        Fri Nov 27 08:58:01 2009 +0300
summary:     Файл readme.txt добавлен в репозиторий
Видим, что в новом репозитории отражени как изменения сделанные локально, так и изменения сделанные в удаленном репозитории, которые мы ранее отправляли командой push. Теперь воспользуемся еще одной командой:
> hg outgoing
comparing with ~/repos/hgproject
searching for changes
changeset:   1:270e49e72f4b
tag:         tip
user:        mike@mike-notebook
date:        Fri Nov 27 10:39:35 2009 +0300
summary:     Записан файл other.txt в другом репозитории
По команде hg outgoing Mercurial выводит список ревизий которые есть в вашем локальном репозитории, но которых нет в «центральном». Отправим появившиеся ревизии в «центральный» репозиторий известным нам способом:
> hg push
pushing to ~/repos/hgproject
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
Итак, в «центральном репозитории две ревизии. Теперь научимся забирать обновления из центрального репозитория. Для этого перейдём в каталог с первым клоном, то есть в ~/projects/hgproject, и скажем:
> hg incoming
comparing with ~/repos/hgproject
searching for changes
changeset:   1:270e49e72f4b
tag:         tip
user:        mike@mike-notebook
date:        Fri Nov 27 10:39:35 2009 +0300
summary:     Записан файл other.txt в другом репозитории
Команда «hg incoming» выдает список ревизий, которые есть в удаленном репозитории, но отсутствуют в локальном. А затем получим эти ревизии, для чего скажем:
> hg pull
pulling from ~/repos/hgproject
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
(run 'hg update' to get a working copy)
Команда «hg pull» получает ревизии из удаленного репозитория, и добавляет их в локальный, таким образом, изменения из нашего «центрального» репозитория были перемещены в  локальный репозиторий. Но они остались только в репозитории, локальная копия осталась нетронутой. Для того, чтобы обновить локальную копию скажем:
> hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
Если посмотреть на состояние рабочей копии, то она соответствует состоянию рабочей копии в репозитории ~/projects/hgproj_clone, а состояние хранилища во всех трех репозиториях одинаково.

Ну и напоследок просто необходимо сказать о ещё одной команде. «hg help», как вы уже, наверное, догадались выводит некоторый набор подсказок по работе с утилитами Mercurial. А при использовании в виде «hg help [command]» выводит подсказку о приемах работы с указанной командой. Причем это именно краткая подсказка, если вам требуется подробное описание, то лучше всего обратиться к документации, которая имеется в абсолютно свободном доступе и вполне неплохого качества.
Итак мы познакомились с основными приемами и командами работы с распределенной системой контроля версий Mercurial. Главным отличием, проявившихся на данном этапе, от централизованных систем контроля версий является наличие полной копии всего репозитория у каждого пользователя, что приводит к двухступенчатой системе взаимодействия с хранилищами (commit-push/pull-update). Такая двухступенчатая система требует некоторого привыкания, однако она вполне понятна и логична, и, на самом деле, достаточно проста в использовании. На этом я заканчиваю этот раздел. В следующем разделе мы рассмотрим наиболее интересные аспекты взаимодействия с Mercurial, а именно ветвления (branching) и слияния (merging).
Progg it

вторник, 24 ноября 2009 г.

Документация Django в chm и pdf формате

Нарыл сегодня документацию для django, да и не только, для того чтобы использовать на ноуте. У меня часто нет интернета на ноуте, и я далеко не все помню из документации, что вполне естественно. А Django-book не совсем нравится, там не все есть, в документации которую рекомендуют использовать на сайте чёрт ногу сломит - всё таки 21 век, а там txt файлы. Вот, малец погуглив, накопал тут.

суббота, 21 ноября 2009 г.

Введение в Mercurial. Часть 1. Распределенные системы контроля версий (DVCS).

Здравствуйте, уважаемые!
Решил написать развернутый цикл статей про Мерк, так как судя по общению с товарищами - как то не очень народ его принимает. Вот буду делиться своим опытом и познаниями. Будет несколько статей, в одну все естественно не влезет. Сегодня просто про распределенные системы контроля версий. Что это и с чем едят.

Думаю что с классическими централизованными системами контроля версий (Subversion, CVS) знакомы уже почти все - есть выделенное специальное хранилище называемое репозиторий, в котором хранятся исходники некоторого проекта, и вся история изменений. И вот к этому хранилищу обращаются попеременно все работающие над проектом.
И вроде бы казалось все замечательно, но не так то все просто. Возникает целая куча проблем, как раз связанная с тем, что репозиторий один, и все в него пытаются закачивать свои исходники.
На мой взгляд главная проблема, к которой постепенно приходят все группы разработчиков - это проблема "длинных коммитов", то есть, в больших командах возможно коммитить только большие части кода, которые покрыты тестами и могут уже использоваться. Тому много причин, но главное - страх поломать что-то готовое в репозитирии, что кем-то используется. Где хранить ваши проходные коммиты не совсем понятно. Есть конечно бранчи, но  в svn это довольно жестокая штука, по крайней мере судя по отзывам использующих людей.

И вот появилась немного более сложная концепция - распределенные системы. Давайте посмотрим на локальную копию svn'a. при выполнении чекаута - у нас в каждом каталоге находится .svn - каталог, в котором хранится копия из репозитория. То есть, в снятом чекаутом наборе каталогов и файлов приходит и точная копия внешнего репозитория. Именно этот принцип и эксплуатируется нещадным образом в распределенных системах - у каждого пользователя есть свой локальный репозиторий, причем вовсе необязательно один. При этом то, что в практике svn назвается коммитом и апдейтом выполняется в свой локальный репозиторий.
За счет локальности коммитов достигается большая гранулярность - теперь можно коммитить не опасаясь поломать чужой код, да и весь проект, при этом вы всегда знаете, что история сохраняется, даже в том случае если вы не имеете доступа к основному репозиторию, например, в случае остуствия доступа в интернет.
Понятие основного репозитория в случае распределенных систем контроля довольно условное. Он основной, потому что некто его так назвал. Ничто не мешает вам взять и забрать обновления лично у Васи Пупкина, а ему у вас, да и отправить свои обновления другому - тоже невелика проблема. Естественно если это позволяют настройки прав доступа. Таким образом получаем, что в распределенных системах отсутствует строгая иерархичность - все репозитории равны, и рядом с каждым репозиторием может быть размещена собственная рабочая копия, хотя и не обязательно.
Смотря на такую структуру, возможность локальных коммитов, возможность синхронизации состояния репозитория с кем угодно создается ощущение, что исходники превратятся в кашу, и на определенном этапе, причем совсем недалеком от начала, уже невозможно будет как-то получить адекватное их состояние. На самом деле все не так страшно. Мощнейшей вещью распределенных систем контроля версий - является ветвление. При этом это не ветвление Subversion, это действительно настоящее, удобное и понятное ветвление и слияние. В DVCS, ну по крайней мере в Mercurial (хотя догадываюсь что и в Git и в Bazaar) ветвление это повседневная операция, это в принципе основа контроля версий в данном случае. И реализована она абсюлютно логично и понятно, и действительно проста в использовании.
Для меня решающим фактором при принятии решения о переходе на Mercurial стали именно локальные коммиты (у меня не всегда есть доступ к интернету, а иногда возникает необходимость что-то закоммитить) и настолько мощная поддержка ветвления. Но уже после перехода я был немало удивлен скоростью работы с репозиториями, эквивалент checkout работает очень быстро даже на сверхбольших репозиториях, commit и update - это моментальные операции, действительно моментальные.
Как работать со всем этим хозяйством я буду постепенно писать в следующих постах. Сегодня этакое начало. Следующий пост будет как раз про основы работы с Mercurial. Для тех кто знаком с Subversion не составит труда сделать первые несколько шагов в этом направлении.

Progg it

суббота, 7 ноября 2009 г.

Установка и настройка системы контроля версий Mercurial на сервере

Встала передо мной задача установить на сервере в инете Mercurial, для организации контроля версий в одном проекте. Причем установить его таким образом, чтобы можно было в ближайшем будущем создавать ещё репозитории с небольшими затратами времени. Также необходимо было обеспечить только авторизованный доступ, так, чтобы можно было ограничить доступ пользователей как на запись, так и на чтение, причем для разных репозиториев по разному.
После часа гугленья нашлось одно решение, которое отвечало всем требованиям - использовать Apache в связке с Mercurial, при помощи hgwebdir.cgi из стандартного комплекта Mercurial. Итак, предположим что на сервере стоит Apache2 и Mercurial. Если этого нет, то нетрудно воспользоваться apt-get (у меня на серваке Debian), или пакетным менеджером своей системы. Отмечу только, что для выполнения всех требований нужна версия Mercurial не меньше 1.2.1, так как только в этой версии была исправлена ошибка, которая не позволяла разделять права на чтение различных репозиториев стандартными средствами.
Итак, основные задачи, которые предстоит решить:
1. Настроить Apache
2. Настроить hgwebdir.cgi
3. Создать и настроить репозиторий
 Поехали с начала. Я разместил корневую папку репозитория в папке /home/mike/www-data/hg/. Чтобы не путаться я на сервере все собственные данные размещаю у себя в домашней папке. В конфиг соответствующего виртуалхоста добавляем описание:
Alias /hg "/home/mike/www-data/hg"
  <Directory "/home/mike/www-data/hg/">
     Order allow,deny
     Allow from all
     AllowOverride All
     Options ExecCGI FollowSymLinks
     AddHandler cgi-script .cgi
  </Directory >
В данном случае предполагается, что все репозитории будут доступны по ссылкам вида http://[домен]/hg/[репозиторий]. AllowOverride необходим для возможности использования .htaccess файла, далее он потребуется. Создаем в каталоге /home/mike/www-data/hg/ файл .htaccess примерно такого содержания:
RewriteEngine On
RewriteBase /hg
RewriteRule ^$ hgwebdir.cgi  [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) hgwebdir.cgi/$1  [QSA,L]

AuthUserFile /home/mike/www-data/hg/htpasswd_hgstore
AuthGroupFile /dev/null
AuthName "HgRepo"
AuthType Basic
Require valid-user
Аутентификацию организуем срествами HTTP, логины и пароли будут храниться в файле /home/mike/www-data/hg/htpasswd_hgstore, как и написано в .htaccess. А в первой части указаны директивы для mod_rewrite (который естественно нужно подключить), которые позволяют получить красивые урлы вида http://[домен]/hg/[репозиторий], без указания hgwebdir.cgi. Правда описанный выше способ имеет свои недостатки - поле [репозиторий] в url не может совпадать с именем каталога на диске.
Идем дальше. В папку /home/mike/www-data/hg/ копируем файлик hgwebdir.cgi (в Debian он находится в /usr/share/doc/mercurial/examples/), и разрешаем ему выполняться:
chmod +x hgwebdir.cgi
Создаем в этом же каталоге файл hgweb.config, в котором прописываем следующее:
[paths]
test = repo_test

[web]
baseurl = /hg
Главное здесь - секция [paths]. В ней прописываются соответствия урлов и репозиториев на диске в виде URL = /path/to/repo, где /path/to/repo считается от текущего по отношению к hgweb.config каталога, а URL - от алиаса /hg/.
Нам потребуется ещё один файлик, а именно hgrc, который тоже создаем в каталоге с hgwebdir.cgi, и в этот самый hgrc вписываем:
[web]
#allow_push = *
#allow_read = *
push_ssl = false
contact = Mike Girkin
description = Get our elephants
allow_archive = bz2 gz zip
style = gitweb
Это заготовка для hgrc в каждом из репозиториев. Осталось дело за малым - создаем репозиторий, и копируем заготовку hgrc по месту:
mkdir repo_test
cd repo_test
hg init
cp ../hgrc repo_test/.hg/
Осталось только создать файлик с паролями htpasswd_hgstore с помощью утилиты htpasswd, и вбить в него нужное количество пользователей и паролей. Ограничить доступ на чтение/запись можно с помощью директив allow_read/allow_push в файлах hgrc лежащих в каждом из репозиториев, например:
allow_read = pupkin petrov sidorov
allow_push = petrov
Обращаю внимание, что в директивах allow_read/allow_push используются те логины, которые забиваются в htpasswd_hgstore, и используются для аутентификации клиентов. Для того, чтобы открыть доступ всем аутентифицировавшимся пользователям пишем allow_read = * или allow_push = *, соответственно.
И последнее, что нельзя забыть сделать, это поменять владельца для всей ~/www-data/hg/ (у меня Apache выполняется от имени www-data:www-data):
chown -R www-data:www-data ~/www-data/hg
На этом, для первого репозитория все настроено, и он должен быть доступен (в данном случае по ссылке http://[домен]/hg/test/, при этом он доступен как для утилиты hg и клиентов по этому url, так и через браузер, хотя необходимость последнего очень спорна.
Чтобы добавить новый репоиторий необходимо выполнить следующее:
1. Создать каталог (mkdir repo_new)
2. Прописать в hgweb.config в секцию [paths] соответсвие путей (new = repo_new)
3. Скопировать hgrc в repo_new/.hg/
4. Прописать во внутренний hgrc пользователей, которым разрешены операции над репозиторием
5. Изменить владельца на www-data (chown -R www-data:www-data repo_new)
На этом все. Спасибо за внимание ;)
Progg it

Используем Backports в Debian Lenny

Backports - это проект по адаптации свежих версий софта для стабильных дистрибутивов Debian. Необходимость в таком переносе объясняется тем, что Debian отличается достаточно длительным релиз-циклом, и софт стабильной ветки сильно устаревает до момента релиза нового дистрибутива.
Давиче мне вот понадобился Mercurial с версией 1.2.1 или позднее, а в репозиториях Lenny только 1.0. Вот и решил написать небольшой пост про подключение и использование проекта backports к Debian Lenny.

1. Добавляем в /etc/apt/sources.list ссылку на репозиторий Backports:
deb http://www.backports.org/debian lenny-backports main contrib non-free
2. Обновляем информацию о пакетах:
apt-get update
3. Поскольку все пакеты из этого репозитория имеют минимальный приоритет, то для того, чтобы поставить пакет из backports, например тот же mercurial, нужно воспользоваться командой:
apt-get -t lenny-backports install mercurial
4. При попытке поставить любой пакет, будет выдаваться угрожающее предупреждение о том, что пакет из неизвестного источника. Избавиться от него легко, поставив пакет с соответствующими ключами (есть альтернативные способы, но я про них говорить не буду):
apt-get install debian-backports-keyring
Всё. Можем ставить требуемые компоненты с версией значительно более близкой к актуальной.

суббота, 17 октября 2009 г.

Отличие между оператором "as" и операцией приведения типа

Это перевод оригинальной статьи находящейся здесь. Все права на оригинал принадлежат автору.

Большинство разработчиков скажут вам, что разница между «(Alpha) bravo» и «bravo as Alpha» состоит в том, что при ошибке приведения типа в первом случае будет выброшено исключение, тогда как во втором случае будет возвращен null. Хотя это действительно так, и это наиболее очевидное отличие, оно не единственно. Есть несколько подводных камней, которых нужно остерегаться.
В-первых, поскольку результатом оператора «as» может быть «null», тип результата должен быть таким, чтобы он в принципе мог принимать значение «null», то есть быть либо ссылочным типом, либо «nullable». Невозможно выполнить «as int» – это бессмысленно. Если аргумент окажется переменной не приводимой к типу «int», то каким же тогда должно быть возвращаемое значение? Так как результат операции «as» может принимать значение «null», то переменная, которой он присваивается, должна быть именованного типа.
Во-вторых, оператор приведения типа cast, это довольно странный зверь. Это связано с двумя противоречащими друг другу действиями: «проверь, действительно ли этот объект заданного типа и выбрось исключение, если нет» и «этот объект не того типа, что указан – найди мне эквивалентное значение указанного типа». Последнее означает, что оператор «cast» не выполняется оператором «as». Если вы напишете:
short s = (short)123;
int? i = s as int?.
вам не светит удача. Оператор «as» не выполнит преобразование, изменяющее представление, из «short» в «nullable int», как бы это сделал оператор приведения типа. Похожим образом, если у вас есть класс Alpha и не связанный с ним класс Bravo, с определённым пользователем преобразованием из Bravo в Alpha, операция «(Alpha) bravo» вызовет заданное пользователем преобразование, а «bravo as Alpha» – нет. Оператор «as» подразумевает только преобразования ссылок – упаковку (boxing) и распаковку (unboxing).
И последнее. Несомненно, случаи применения эти двух операторов внешне похожи, но по смыслу (семантически) очень различаются. Оператор приведения типа сообщает читателю: «Я уверен, что это преобразование корректно, и готов обработать исключение, если это не так». Оператор «as» говорит иное: «Я не знаю, корректно ли это преобразование или нет – давайте попробуем и посмотрим, что из этого выйдет».

среда, 26 августа 2009 г.

Windows.Forms: ложимся в трей

Не так давно встала передо мной задача - уложить программу на C# в трей. При разработке использовались стандартный Windows.Forms из .net 3.5, хотя со времен .net 2.0 он практически не изменился. На моё счастье все оказалось очень просто. Попытаюсь объяснить основные шаги.
1. Размещаем компонент NotifyIcon на форме. У меня в приложении одна форма главная, остальные даже в таскбаре не отображаются, на главную форму и положил. NotifyIcon находится в ToolBox, вместе со всеми контролами... Параметры этого NotifyIcon говорят сами за себя, правда если не задать иконку, то в трее вообще ничего не появится :)
2. Маленько изменяем поведение формы. Для программ размещающихся в трее принято при сворачивании убирать окно из таскбара, а при щелчке на иконке в таскбаре - разворачивать. Поэтому пишем следующие обработчики:
- для сворачивания формы (эвент Form.Resize)
  1. private void MainForm_Resize(object sender, EventArgs e)
  2. {
  3.     if (FormWindowState.Minimized == WindowState) Hide();
  4. }
* This source code was highlighted with Source Code Highlighter.
- и для клика по иконке в трее (эвент NotifyIcon.Click)
  1. private void notifyIcon_Click(object sender, EventArgs e)
  2. {
  3.     Show();
  4.     WindowState = FormWindowState.Normal;
  5. }
* This source code was highlighted with Source Code Highlighter.
3. Изменим поведение при попытке юзера закрыть окно по крестику. Для этого используем обработчик эвента Form.Closing:
  1. private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
  2. {
  3.  if (e.CloseReason == CloseReason.UserClosing)
  4.  {
  5.     e.Cancel = true;
  6.     Hide();
  7.     WindowState = FormWindowState.Minimized;
  8.  }
  9. }
* This source code was highlighted with Source Code Highlighter.
То есть, при щелчке по крестику наша форма просто свернется вниз, а завершение программы будет отменено. Правда на текущий момент эту программу можно только прибить из менеджера задач, что не очень хорошо. Поэтому:
4. Сделаем контекстное меню для иконки в трее. Компонент ContextMenuStrip размещаем на нашей форме, создаем в нем пункт "Выход" (и какие ещё необходимо). Затем скажем нашему NotifyIcon, что у него есть контекстное меню с помощью свойства NotifyIcon.ContextMenuStrip. А обработчик пункта "Выход" у нас будет таким:
  1. private void miClose_Click(object sender, EventArgs e)
  2. {            
  3.     Application.Exit();
  4. }
* This source code was highlighted with Source Code Highlighter.
То есть CloseReason в данном случае будет ApplicationExitCall.
Вот и всё. Приложение должно вполне успешно справляться со своей задачей "лежания" в трее.

пятница, 21 августа 2009 г.

Генераторы в C# или "бесполезное" yield

Язык о котором пойдёт речь вышел достаточно давно, однако, судя по реальному опыту общения, мало кто пользуется всеми имеющимися возможностями. Этот пост я посвящу ключевому слову yield в языке C# 2.0.
Сначала несколько слов о тенденциях развития С#. Вспоминая каким был C# 1.0, могу сказать, что это была калька с Java2SE, причем не сказать что удачная. Чистый ООП язык, практически слизаный с Java, без какой-либо собственной красоты и лоска, без своего шарма. Однако уже вторая версия очень сильно порадовала своими возможностями, а именно некоторым движением в сторону функционального программирования. Эта же тенденция существенно продолжается и в третьей версии (VS2008), в C# 4.0 (VS2010) функциональные возможности будут расширены ещё больше, при этом Microsoft добавляет в платформу .NET полноценный функциональный язык, который много лет разрабатывался Microsoft Research. По-моему говорит это о многом.
А теперь к делу!
Ключевое слово yield изначально было предназначено для облегчения разработки классических итераторов .NET, а именно классов, реализующих интерфейсы IEnumerator<T> и IEnumerable<T>. То есть, разработка своей коллекции, или способа обращения к существующей коллекции требовала разработки как минимум одного класса - итератора с интерфейсом IEnumerator<T>, по одному классу на каждый способ прохода коллекции. Довольно муторное и однообразное занятие.
И тут к нам на помощь приходит yield!
Небольшой пример. Пусть нам хочется получить все элементы списка находящиеся в диапазоне [3;5]. Вместо стандартного for напишем следующий кусок:

IEnumerator<int> GetIntervalled(List<int> Collection)
{
for(int i=0;i<Collection.Count;i++)
{
if(Collection[i]>=3 && Collection[i]<=5) yield return Collection[i];
}
}

Использовать только что написанный код можно как и обычно используются итераторы – foreach. При очередном выполнении yield return функция вернет управление вызывающей, и передаст соответствующий элемент. На самом деле компилятор по этому коду создаст виртуальный класс реализующий интерфейс IEnumeratot<t>, но нас это не должно интересовать. Подобная конструкция есть первый, маленький шажок в сторону функционального программирования – это один из вариантов продолжения (continuations), потому что визуально код, при повторном вызове, продолжает выполняться с того места где выполнение было окончено. Подобная конструкция в некоторых языках программирования (откуда она и была заимствована) называется генератором, отсюда и название заголовка :)
Конечно, приведенный выше пример абсолютно неинтересен, и ничем не отличается по смыслу от примера, приводимого в документации. Понять, зачем применять столь хитрую конструкцию крайне тяжело. Для того, чтобы стало немножко больше понятно зачем нужны такие вещи приведу пример из реального кода.
В формочку (Windows.Forms) выводятся некоторые сущности, а пользователь отмечает галочками те, которые ему необходимы. Мне хотелось получить отмеченные галочкой сущности, yield пригодился как нельзя кстати:

public IEnumerable<DatabaseImportCase> SelectedImportCases
{
get
{
for (int i = 0; i < lbImportPackages.CheckedIndices.Count; i++)
{
yield return ShownImportCases[i];
}
}
}

ShownImportCases – это список отображаемых сущностей, lbImportPackages – листбокс с галочками (CheckedListBox), в котором отображаются эти сущности. Естественно предполагается, что индексы отображаемых на форме и хранимых в памяти сущностей совпадают. Операции по получению элементов происходят с помощью «ленивых вычислений», то есть получение очередного элемента произойдет только тогда, когда вы его потребуете. Никаких виртуальных списков в памяти не строится. Итак, yield – это универсальный, очень удобный способ получения новых, произвольных по сложности итераторов. Основное его преимущество – уменьшение количества не имеющего смысловой нагрузки кода. То есть, использование этого приема, это просто syntax sugar, но вряд ли кто откажется от сладкого :) Безусловно появление LINQ, а именно LINQ to Objects существенно снизило ценность yield, поскольку практически все итераторы можно получить с помощью запросов LINQ, однако понимание работы такого рода продолжений крайне необходимо для понимания более сложных конструкций функционального программирования. Которые я постараюсь рассмотреть в одном из следующих постов.

понедельник, 17 августа 2009 г.

Восстанавливаем Grub после установки Windows

На днях решил попробовать установить Windows 7, и, вполне естественно столкнулся с проблемой. Сама Windows 7 встала без проблем, установка существенно проще чем в ХР (Vista не видел - ничего сказать не могу). Но, вполне в традициях Microsoft, семёрка не замечает никаких операционных систем на компьютере кроме Windows. Естественно мне хотелось сохранить всё, что у меня есть, а именно Windows XP и KUbuntu 9.04, ну и естественно настроить мультизагрузку.
Как и ожидалось после установки семерки Grub был затёрт напрочь, хотя выбор между XP и Windows 7 все же был. Ситуация осложняется ещё и тем, что у меня 2 жестких диска, и я абсолютно не помню как там у меня что загружается. Стандартное решение выдаваемое гуглом в поиске, а именно "grub-install" не прокатывает. По неизвестным мне причинам ни в одном блоге, который приводит grub-install как решение не написано что нужно передавать параметр - диск, на который ставить grub. Погуглив ещё малец, я нашел решение, которое сам когда-то использовал, но успешно забыл :)
Итак:
1. Загружаемся с Live CD
2. Монтируем раздел с линуксом на винте, примерно так:
cd /mnt
sudo mkdir linux
sudo mount /dev/sda1 /mnt/linux
sudo mount --bind /mnt/linux/boot /boot
3. Заходим в консоль команд Grub:
sudo grub
Дальше команды выполняются в консоли grub
4. Ищем где у нас стоит Stage1:
find /boot/grub/stage1
Вернется что-то типа (hdx, y), вот эти магические x,y и используем:
5. Говорим Grub'у где у него Stage1, и куда ему поставится:
root (hdx,y)
setup (hdx)

Итак grub мы восстановили. Осталось только добавить Windows 7 в список загрузки. Идем в /boot/grub/menu.lst, и смотря на описание загрузки Windows XP сооружаем примерно то же самое, сделав поправку "на ветер". У меня получилось вот такое:
title Windows 7 RC
root (hd1,5)
chainloader +1
makeactive

Всё. Пользуемся 3 ОС одновременно. Но лично я собираюсь перебираться из XP в семерку :)

пятница, 1 мая 2009 г.

Новая фишечка от Google в поиске

Надо было сегодня кое-что погуглить, и обратил внимание на некоторе обновление страницы поиска в гугле. Фактически добавили комментарии к результатам поиска, и возможность перемещать/удалять результаты поиска для повторного использования. Интересно будет посмотреть насколько это будет удобно в реальности, и как быстро подобная вещь появится у яндекса :)
И очень интересно когда же это было добавлено. Учитывая мою клиническую невнимательность к мелочам - вполне возможно пару месяцев назад :)


среда, 8 апреля 2009 г.

Наследование шаблонов в Django

В питоновском веб-фреймворке django встроен механизм наследования шаблонов страниц. Лично я считаю, что это очень спорная фишка и должна применяться с большой аккуратностью. Сейчас попробую объяснить почему.
Сначала определимся с терминологией. Этот механизм является именно наследованием, тем самым, которое используется в ООП. Этот вывод я сделал проанализировав получающиеся конструкции. Ведь фактически шаблон-наследник знает о структуре шаблона предка, хотя может и не знать детали реализации, ведь для того, чтобы переопределить (или доопределить) часть исходного шаблона нам потребуется указать имена тех частей, которые необходимо переопределить. Итак, рассмотрим маленкий пример:
Родительский шаблон:
<html>
<head>
<meta equiv="Content-type" content="text/html; charset=utf-8" />
<link type="text/css" rel="stylesheet" href="/static/Styles.css" />
{% block extrahead %} {% endblock %}
<title>{% block title %} {% endblock %}</title>
</head>
<body>
{% block content %} {% endblock %}
</body>
</html>
Шаблон наследник:
{% extends "base.html" %}

{% block extrahead %}
<script type="text/javascript" src="additionalscript.js"></script>
{% endblock %}

{% block title %}
Супер заголовок
{% endblock %}

{% block content %}
...
{% endblock %}

Казалось бы все по джанговски и замечательно. Но! Работа с шаблонами реализованная таким образом таит немало неприятностей, но самая главная из них - шаблон более низкого уровня по иерархии знает о структуре шаблонов более высокого уровня. И не просто знает (что не стало бы проблемой при использовании подходящих конвенций), а просто обязан использовать эти сведения. Получается что шаблон более низкого уровня сильно связан с более абстрактным шаблоном, что не есть правильно и лишает подобную структуру гибкости.
Выход? Есть выход! Наследование шаблонов безусловно мощная вещь и позволяет избежать дублирования кода, но использовать её необходимо с аккуратностью, особенно в больших проектах. Использовать также, как и наследование ООП, проверяя действие принципов используемых в ООП. Лично я для себя проверяю возможность наследования отношением "является", наследование можно применять в случае, если в человеческом языке корректной будет фраза "<наследник> является <предком>". Таким образом, приведенный выше пример корректен, так как шаблон-предок, фактически, является "обобщенной страницей", а шаблон наследник - "конкретной страницей". Но в случаях добавления дополнительных элементов на страницу, контролов и прочего, на мой взгляд более правильным и менее пахнущим будет использование агрегации с использованием механизмов custom tags.

понедельник, 6 апреля 2009 г.

Как подружить Mail.RU Agent и Kopete

Возникла необходимость подружить популярный в россии мессенджер Mail.RU Agent (MRA) с Linux. Так уж сложилось, что использую я KDE4, соответственно мессенджер у меня - Kopete. То есть требуется сделать так, чтобы используя Kopete можно было легко и непринужденно общаться с контактами использующими Mail.RU Agent. Ведь не всякую девушку убедишь перейти на "идеологически правильный" Jabber :)
Малец погуглив нашлось вот это, но люди, считающие себя очень умными, зачастую не могут объяснить по человечески :) Я вот вроде бы не чайник, но фраза: "зарегестрируйся сначало на jabber.ru , потом войди туда, и в обзоре сервисов введи jabber.ru и там в списке будет mrim.jabber.ru и на нем и регистрируй транспорт" для меня загадка.
Делюсь своим решением, постараюсь объяснить кратко, но понятно. Сначала оговорюсь, что использую я KUbuntu Linux 8.10, и Kopete из его репозиториев.
Решение поставленной задачи заключается в использовании сервера jabber.ru как шлюза для передачи сообщений из/в MRA. Откуда шаг номер раз:
1. Регистрируемся на jabber.ru
Шаг номер два не менее очевиден, исходя из краткого описания предложенного решения:
2. Добавляем вновь созданную учётку в Kopete
Выглядеть должно примерно так (красным выделена вновь созданная учётка):А вот теперь настало время для расшифровки той фразы. Третий шаг следует как раз из нее:
3. Необходимо добавить сервис mrim.jabber.ru
Для этого в окне контакт-листа ищем кнопку , и смело и решительно жмём её. Должно выползти следующее:
Правой кнопочкой щёлкаем на учётке Jabber.RU, и выбираем пункт "Службы". Появляется диалоговое окошко, в котором сначала жмём "Запросить сервер", и если всё до этого было сделано правильно, то появится список сервисов, из которых нам интересен mrim.jabber.ru. Выбираем его, снова клацаем правой кнопочкой, "Зарегистрировать". Закрываем окошко, и нам приходит сообщение что mrim.jabber.ru добавил нас в свой контакт-лист. Замечательно! Добавим и мы его. Как только он появится в вашем контакт листе на вас посыпятся сообщения о том, что вас добавили те, кто находится в вашем контакт-листе в MRA. Всё. С этого момента должно работать. Заметьте, что у вас появилась ещё одна учётка:

Поехали!

Вообще говоря я достаточно давно думал завести свой блог, посвященный различным вопросам IT-сферы, и вот всё таки разродился. Постараюсь выкладывать тут материалы, которые мне показались интересными, и могут кому-то оказаться полезными.
Мои интересы в IT-сфере - это в первую очередь программирование с использованием различных инструментов, для различных платформ и целевых аудиторий; интересуюсь также Linux'ом, и общими вопросами IT.
Буду рад осмысленным комментариям, критике и корректным дискуссиям. Кащенизм и прочие проявления интеллектуальной импотенции будут нещадно удаляться.