<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ZetBlog&#187; ORM</title>
	<atom:link href="http://zetblog.ru/tag/orm/feed/" rel="self" type="application/rss+xml" />
	<link>http://zetblog.ru</link>
	<description>Зеты говорят. Блог о программировании, администрировании и безопасности.</description>
	<lastBuildDate>Sat, 29 Oct 2011 18:59:40 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Hibernate: Удаление объектов из БД.</title>
		<link>http://zetblog.ru/programming/201002/hibernate-%d1%83%d0%b4%d0%b0%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5-%d0%be%d0%b1%d1%8a%d0%b5%d0%ba%d1%82%d0%be%d0%b2-%d0%b8%d0%b7-%d0%b1%d0%b4/</link>
		<comments>http://zetblog.ru/programming/201002/hibernate-%d1%83%d0%b4%d0%b0%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5-%d0%be%d0%b1%d1%8a%d0%b5%d0%ba%d1%82%d0%be%d0%b2-%d0%b8%d0%b7-%d0%b1%d0%b4/#comments</comments>
		<pubDate>Wed, 10 Feb 2010 10:35:29 +0000</pubDate>
		<dc:creator>lizz</dc:creator>
				<category><![CDATA[Программирование]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Jython]]></category>
		<category><![CDATA[ORM]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://zetblog.ru/?p=921</guid>
		<description><![CDATA[По работе пришлось изучать работу Java&#8217;вского ORM&#8217;а, пока что знаком только бегло, но некоторые полезные штуки для новичка решил записать. Может кому пригодится, может нет, но хотя бы самому гуглить придётся меньше =). Кстати, примеры будут не на Java, а на Jython&#8217;е, т.к. с первым я тоже не очень знаком :). Итак, опустим настройку и [...]]]></description>
			<content:encoded><![CDATA[<p>По работе пришлось изучать работу Java&#8217;вского ORM&#8217;а, пока что знаком только бегло, но некоторые полезные штуки для новичка решил записать. Может кому пригодится, может нет, но хотя бы самому гуглить придётся меньше =). Кстати, примеры будут не на Java, а на Jython&#8217;е, т.к. с первым я тоже не очень знаком :).</p>
<p>Итак, опустим настройку и подключение Hibernate, предположим, что вы это уже сделали :). Быть может, напишу об этом позже. Требуется удалить из таблицы player (которая маппится с классом Player, естественно&#8230; или наоборот :) ) некоторые записи. Если мы знаем id игрока, тогда всё элементарно (как и в других случаях)):</p>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;">player = session.<span class="me1">load</span><span class="br0">&#40;</span>Player, player_id<span class="br0">&#41;</span><br />
session.<span class="me1">delete</span><span class="br0">&#40;</span>player<span class="br0">&#41;</span></div>
</div>
<p>Теперь рассмотрим случай, когда надо удалить много записей.<span id="more-921"></span></p>
<p>В ORM&#8217;е Hibernate несколько способов провернуть это. 1й &#8212; очевидный, с помощью createQuery выбрать все нужные записи HQL&#8217;ом и в цикле удалять по одной:</p>
<pre>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;">players_list = session.<span class="me1">createQuery</span><span class="br0">&#40;</span><span class="st0">&quot;FROM Player WHERE name = :name&quot;</span><span class="br0">&#41;</span>\
&nbsp; &nbsp; .<span class="me1">setParameter</span><span class="br0">&#40;</span><span class="st0">&quot;name&quot;</span>, <span class="st0">&quot;Василий&quot;</span><span class="br0">&#41;</span>\
&nbsp; &nbsp; .<span class="kw2">list</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
<span class="kw1">for</span> player <span class="kw1">in</span> players_list:
&nbsp; &nbsp; session.<span class="me1">delete</span><span class="br0">&#40;</span>player<span class="br0">&#41;</span></div>
</div>
</pre>
<p>Но это влечёт накладные расходы на получение всех нужных объектов, а затем удаление по одному, в таком случае мы выполним столько sql-запросов, сколько объектов загрузили, что может ухудшить производительность приложения. Для этого есть другой способ. Вот в общем то и он:</p>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;">session.<span class="me1">createQuery</span><span class="br0">&#40;</span><span class="st0">&quot;DELETE Player WHERE name = :name&quot;</span><span class="br0">&#41;</span>\<br />
&nbsp; &nbsp; .<span class="me1">setParameter</span><span class="br0">&#40;</span><span class="st0">&quot;name&quot;</span>, <span class="st0">&quot;Василий&quot;</span><span class="br0">&#41;</span>\<br />
&nbsp; &nbsp; .<span class="me1">executeUpdate</span><span class="br0">&#40;</span><span class="br0">&#41;</span></div>
</div>
<p>Вот таким способом можно избавиться от всех неугодных нам Василиев ;). Так же есть возможность воспользоваться сырым sql с помощью Session.createSQLQuery().</p>
<p>Ссылки:<br />
<a href="http://docs.jboss.org/hibernate/stable/core/reference/en/html/batch.html#batch-direct">Документация по Hibernate</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://zetblog.ru/programming/201002/hibernate-%d1%83%d0%b4%d0%b0%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5-%d0%be%d0%b1%d1%8a%d0%b5%d0%ba%d1%82%d0%be%d0%b2-%d0%b8%d0%b7-%d0%b1%d0%b4/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>PHP: Пример использования ORM Doctrine.</title>
		<link>http://zetblog.ru/programming/200905/php-orm-doctrine-simple-example/</link>
		<comments>http://zetblog.ru/programming/200905/php-orm-doctrine-simple-example/#comments</comments>
		<pubDate>Wed, 20 May 2009 10:35:25 +0000</pubDate>
		<dc:creator>lizz</dc:creator>
				<category><![CDATA[web]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[Doctrine]]></category>
		<category><![CDATA[ORM]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://zetblog.ru/?p=700</guid>
		<description><![CDATA[В данном посте рассмотрим простенький пример использования ORM, а именно, Doctrine. Если кто не в курсе, то ORM (Object-Relational Mapping, объектно-реляционная проекция) &#8212; такая штука, которая обеспечивают классам прозрачный доступ к базе данных. Правда не всяким классам, а тем, которые представляют описание нашей модели данных. В общем мне бы пару лет назад узнать о такой [...]]]></description>
			<content:encoded><![CDATA[<p>В данном посте рассмотрим простенький пример использования ORM, а именно, Doctrine. Если кто не в курсе, то ORM (Object-Relational Mapping, объектно-реляционная проекция) &#8212; такая штука, которая обеспечивают классам прозрачный доступ к базе данных. Правда не всяким классам, а тем, которые представляют описание нашей модели данных. В общем мне бы пару лет назад узнать о такой штуке, может быть я и не забросил изучение php, и вообще, много чего полезного сделал :D. Если кто-то знаком с паттернами проектирования, то можно сказать, что Doctrine соответствует шаблону Active Record. К своему стыду, сам я не знаком с ними, поэтому ничего конкретней сказать не могу пока что :-).</p>
<p>Кстати, мы уже затрагивали мельком тему ORM, но только для python. Можете посмотреть <a href="http://zetblog.ru/tag/sqlalchemy/">тут</a> про SQLAlchemy.</p>
<p>Далее будет рассмотрен пример написания модуля на php с использованием ORM Doctrine для отправки личных сообщений между пользователей.<br />
<span id="more-700"></span></p>
<p>Для начала определимся с нашей таблицей. Я решил использовать всего одну табличку, и для каждого сообщения так же хранить только одну запись в БД. И так, поля таблицы выглядят у меня примерно следующим образом:</p>
<ul>
<li>id &#8212; id сообщения;</li>
<li>uid_from &#8212; id отправившего пользователя;</li>
<li>uid_to &#8212; id получателя;</li>
<li>title &#8212; заголовок сообщения;</li>
<li>text &#8212; текст сообщения;</li>
<li>flag_unread &#8212; флаг нового сообщения;</li>
<li>flag_del_from &#8212; признак удалённого сообщения из исходящих;</li>
<li>flag_del_to &#8212; признак удалённого сообщения из входящих;</li>
<li>date &#8212; дата отправки в формате unix timestamp;</li>
</ul>
<p>На этом этап проектирования у меня закончился :). Теперь перейдём к технической стороне вопроса.</p>
<p>Качаем тот самый Doctrine (ссылка в конце поста), находим в архиве директорию, где лежит файл Doctrine.php и копируем всё содержимое этого каталога в отдельную папку в нашем проекте (я скопировал в doctrine). Следуя документации, создаём файл bootstrap.php в корне проекта с таким содержанием:</p>
<pre class=".brush: php">&lt;?php
require_once(dirname(__FILE__) . '/doctrine/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_MODEL_LOADING, Doctrine::MODEL_LOADING_CONSERVATIVE);
$manager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);
$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);
$manager->setAttribute(Doctrine::ATTR_QUOTE_IDENTIFIER, true);
$user = 'tst';
$password = 'tst';
$host = 'localhost';
$db = 'tst';
$dsn = "mysql://$user:$password@$host/$db";
$conn = Doctrine_Manager::connection($dsn);
Doctrine::loadModels('models');
?></pre>
<p>Тут настраивается подключение к БД и некоторые параметры (&#171;ленивый&#187; способ загрузки моделей, валидация данных и т.п.). Последняя строчка указывает Doctrine где искать наши модели.</p>
<p>Теперь в корне проекта создаём папку models и в ней файл PrivateMessage.php. Учтите, что имя файла и класса, который описан в нём, должны совпадать. Вот как у меня выглядит этот файл:</p>
<pre class=".brush: php">&lt;?php
class PrivateMessage extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('uid_from', 'integer', 8, array(
				'unsigned' => true,
				'notnull' => true
			)
		);
        $this->hasColumn('uid_to', 'integer', 8, array(
				'unsigned' => true,
				'notnull' => true
			)
		);
        $this->hasColumn('title', 'string', 128);
        $this->hasColumn('text', 'string', 255);
        $this->hasColumn('flag_unread', 'boolean', array(
        		'default' => false,
				'notnull' => true
			)
		);
	$this->hasColumn('flag_del_from', 'boolean', array(
				'notnull' => true,
				'default' => false
			)
		);
	$this->hasColumn('flag_del_to', 'boolean', array(
				'notnull' => true,
				'default' => false
			)
		);
        $this->hasColumn('date', 'integer', 8, array(
        		'notnull' => true,
				'default' => time()
			)
		);
    }
}
?></pre>
<p>Тут мы не описали поле id, т.к. Doctrine создаст его автоматически, если не указано других первичных ключей (primary key). Так же в таблице не указаны индексов, что не есть хорошо. Неплохо было бы сделать их для полей uid_from и uid_to хотя бы. Но не сделаем, ибо лень, как-нибудь в другой раз ;-).</p>
<p>Следующим шагом создаём в корне проекта файл test.php, в котором будут описаны основные функции для работы с личными сообщениями и некоторый код для демонстрации функционала. Вот содержимое этого файла:</p>
<pre class=".brush: php">&lt;?php
require_once('bootstrap.php');
$conn->export->exportClasses(array('PrivateMessage'));
#FIXME: Set current user ID from session.
$curr_uid = 1;
function get_messages($inbox, $uid) {
	/*
	 * If $inbox == true then returns inbox of user $uid,
	 * else returns outbox.
	 */
	if($inbox) {
		$q = 'pm.uid_to = ? AND pm.flag_del_to = ?';
	} else {
		$q = 'pm.uid_from = ? AND pm.flag_del_from = ?';
	}
	return Doctrine_Query::create()
    	->from('PrivateMessage pm')
    	->where($q, array($uid, 0))
    	->fetchArray();
}

function get_message($id, $uid) {
	$q = Doctrine_Query::create()
    	->from('PrivateMessage pm')
    	->where('pm.id = ? AND (pm.uid_from = ? OR pm.uid_to = ?)', array($id, $uid, $uid));
    	$q = $q->fetchArray();
    	if(count($q)) {
    		if($uid == $q[0]['uid_to'] &#038;&#038; $q[0]['flag_unread']) {
    			Doctrine_Query::create()
				    ->update('PrivateMessage pm')
    				->set('pm.flag_unread', '?', false)
 			   		->where('pm.id = ?', $q[0]['id'])
 			   		->execute();
    		}
    		return $q[0];
    	} else {
    		return false;
    	}
}

function delete_messages($ids, $uid) {
	/*
	 * $ids - array of ids of messages, that will be deleted.
	 * $uid - current user id.
	 * P.S. Broken English - is the most popular language in the world ;-).
	 */
	foreach($ids as $id) {
		Doctrine_Query::create()
			->update('PrivateMessage pm')
			->set('pm.flag_del_to', '?', true)
 			->where('pm.id = ? AND pm.uid_to = ?', array($id, $uid))
 			->execute();
 		Doctrine_Query::create()
			->update('PrivateMessage pm')
			->set('pm.flag_del_from', '?', true)
 			->where('pm.id = ? AND pm.uid_from = ?', array($id, $uid))
 			->execute();
	}
	Doctrine_Query::create()
		->delete('PrivateMessage pm')
 		->where('pm.flag_del_from = 1 AND pm.flag_del_to = 1')
 		->execute();
}

function send_message($to, $title, $text, $uid) {
	$pm = new PrivateMessage();
	$pm->uid_from = $uid;
	$pm->uid_to = $to;
	$pm->title = $title;
	$pm->text = $text;
	$pm->flag_unread = true;
	// Если хранить в исходящих ненадо,
	// то можно установить следующий флаг в true.
	$pm->flag_del_from = false;
	$pm->flag_del_to = false;
	$pm->save();
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
&lt;?php
echo 'Current user:' . $curr_uid;
// Send message section.
if((int)@$_REQUEST['to'] &#038;&#038; !@empty($_REQUEST['title']) &#038;&#038; !@empty($_REQUEST['text'])) {
	send_message((int)$_REQUEST['to'], $_REQUEST['title'], $_REQUEST['text'], $curr_uid);
}
// End of send message section.

// Delete messages section.
if(@count($_REQUEST['del_messages'])) {
	echo '
<pre>';
	delete_messages($_REQUEST['del_messages'], $curr_uid);
	echo '</pre>
<p>';<br />
}<br />
// End of delete messages section.</p>
<p>// Inbox section.<br />
?></p>
<hr/>Inbox:</p>
<form name="del_messages" action="" method="post">
<table border="1">
<tr>
<td>Del</td>
<td>Message ID</td>
<td>Date</td>
<td>From</td>
<td>Title</td>
</tr>
<p>&lt;?php<br />
$m = get_messages(true, $curr_uid);<br />
foreach($m as $message) {<br />
	// Mark as bold unread messages.<br />
	if($message['flag_unread']) {<br />
		$b_open = '<b>';<br />
		$b_close = '</b>';<br />
	} else {<br />
		$b_open = '';<br />
		$b_close = '';<br />
	}<br />
	// End mark.<br />
	echo "<br />
<tr>
<td>
<input type=\"checkbox\" name=\"del_messages[]\" value=\"$message[id]\"/></td>
<td><a href=\"?message_id=$message[id]\">$b_open$message[id]$b_close</a></td>
<td>$b_open" . date('d.m.y H:i', $message['date']) . "$b_close</td>
<td>$b_open$message[uid_from]$b_close</td>
<td>$b_open$message[title]$b_close</td>
</tr>
<p>";<br />
}<br />
?></p>
<tr>
<td colspan="5">
<input type="submit" value="Delete selected"/></td>
</tr>
</table>
<p>&#038;lt?php<br />
// End of inbox section.</p>
<p>// Outbox section.<br />
?></p>
<hr/>Outbox:</p>
<table border="1">
<tr>
<td>Del</td>
<td>Message ID</td>
<td>Date</td>
<td>To</td>
<td>Title</td>
</tr>
<p>&lt;?php<br />
$m = get_messages(false, $curr_uid);<br />
foreach($m as $message) {<br />
	// Mark as bold unread messages.<br />
	if($message['flag_unread']) {<br />
		$b_open = '<b>';<br />
		$b_close = '</b>';<br />
	} else {<br />
		$b_open = '';<br />
		$b_close = '';<br />
	}<br />
	// End mark.<br />
	echo "<br />
<tr>
<td>
<input type=\"checkbox\" name=\"del_messages[]\" value=\"$message[id]\"/></td>
<td><a href=\"?message_id=$message[id]\">$b_open$message[id]$b_close</a></td>
<td>$b_open" . date('d.m.y H:i', $message['date']) . "$b_close</td>
<td>$b_open$message[uid_to]$b_close</td>
<td>$b_open$message[title]$b_close</td>
</tr>
<p>";<br />
}<br />
?></p>
<tr>
<td colspan="5">
<input type="submit" value="Delete selected"/></td>
</tr>
</form>
</table>
<p>?><br />
// End of outbox section.</p>
<p>// View message section.<br />
if((int)@$_REQUEST['message_id']) {<br />
	$m = get_message($_REQUEST['message_id'], $curr_uid);<br />
	if($m !== false) {<br />
		echo "<br />
<hr/>View message #$m[id]:";<br />
		echo '<br />
<table border="1">';<br />
		echo "<br />
<tr>
<td>From:</td>
<td>$m[uid_from]</td>
</tr>
<tr>
<td>To:</td>
<td>$m[uid_to]</td>
</tr>
<tr>
<td>Title:</td>
<td>$m[title]</td>
</tr>
<tr>
<td>Message:</td>
<td>$m[text]</td>
</tr>
</table>
<p>";<br />
	}<br />
}<br />
// End of view message section.</p>
<p>// Send message section.<br />
echo "</p>
<hr/>New message:</p>
<table border="1">
<form action="" method="post" name="send_message">
<tr>
<td>From:</td>
<td>$curr_uid</td>
</tr>
<tr>
<td>To:</td>
<td>
<input name="to"/></td>
</tr>
<tr>
<td>Title:</td>
<td>
<input name="title"/></td>
</tr>
<tr>
<td>Message:</td>
<td><textarea name="text"></textarea></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="send message"></td>
</tr></form>
</table>
<p>";<br />
// End of send message section.<br />
?><br />
</body><br />
</html></pre>
<p>$conn->export->exportClasses(array(&#8216;PrivateMessage&#8217;)) &#8212; это строка создаст таблицу private_messaages, если таковой ещё нет. Так что можно её перенести в установочный скрипт, но поскольку у нас нет такого, то мы её оставим.<br />
$curr_uid &#8212; эта переменная по идее должна браться из сессии. Как она должна называться в сессии я не знал, поэтому присвоил ей значение в коде 1.<br />
get_messages($inbox, $uid) &#8212; возвращает все входящие сообщения пользователя $uid, если $inbox равна true, иначе &#8212; исходящие. Неплохо было бы добавить лимит на выборку записей (например, с 1й по 20ю, с 21й по 40ю), но поскольку этот модуль нигде применять не планирую, то и доделывать его нет желания. Если кому то надо &#8212; пишите в комменты ;-).<br />
get_message($id, $uid) &#8212; возвращает сообщение $id, $uid передаётся во все функции для проверки принадлежности сообщений текущему пользователю. Ещё раз повторю, идентификатор ползьователя должен браться из сессии.<br />
delete_messages($ids, $uid) &#8212; $ids &#8212; массив id&#8217;шников сообщений, которые надо удалить. Реально удаляются записи из БД только те, у которых flag_del_from и flag_del_to равны 0.<br />
send_message($to, $title, $text, $uid) &#8212; $to &#8212; id получателя, $uid &#8212; отправителя, остально думаю понятно.<br />
Дальше по коду идёт часть с html-выводом и обработкой переданных парамтеров, так что ничего интересного. Код по моему везде достаточно просто, так что подробно объяснять не стал. Если вопросы остались &#8212; в комменты.</p>
<p>P.S. Это моя первая попытка сделать что-либо с использованием Doctrine :).<br />
Ссылки к статье:<br />
<a href="http://ru.wikipedia.org/wiki/ORM">http://ru.wikipedia.org/wiki/ORM</a> &#8212; ORM;<br />
<a href="http://www.doctrine-project.org/">http://www.doctrine-project.org/</a> &#8212; официальный сайт проекта Doctrine.</p>
]]></content:encoded>
			<wfw:commentRss>http://zetblog.ru/programming/200905/php-orm-doctrine-simple-example/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Python: Работа с потоками. Часть 2.</title>
		<link>http://zetblog.ru/programming/200812/python-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0-%d1%81-%d0%bf%d0%be%d1%82%d0%be%d0%ba%d0%b0%d0%bc%d0%b8-%d1%87%d0%b0%d1%81%d1%82%d1%8c-2/</link>
		<comments>http://zetblog.ru/programming/200812/python-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0-%d1%81-%d0%bf%d0%be%d1%82%d0%be%d0%ba%d0%b0%d0%bc%d0%b8-%d1%87%d0%b0%d1%81%d1%82%d1%8c-2/#comments</comments>
		<pubDate>Sat, 20 Dec 2008 22:15:27 +0000</pubDate>
		<dc:creator>lizz</dc:creator>
				<category><![CDATA[параллельное]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[ORM]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[SQLAlchemy]]></category>
		<category><![CDATA[threading]]></category>

		<guid isPermaLink="false">http://zetblog.ru/?p=313</guid>
		<description><![CDATA[Мы уже недавно рассматривали примитивную работу с потоками, а именно &#8212; запуск потоков и передача им параметров, использование замков и, по большому счёту, всё. Теперь пришло время изучить другие способы взаимодействия потоков в python&#8217;е. Для лучшего понимания советую прочитать этот пост. Решим ту же самую задачу, что и в предыдущем посте, но несколько изменив условия. [...]]]></description>
			<content:encoded><![CDATA[<p>Мы уже недавно рассматривали примитивную работу с потоками, а именно &#8212; запуск потоков и передача им параметров, использование замков и, по большому счёту, всё. Теперь пришло время изучить другие способы взаимодействия потоков в python&#8217;е.</p>
<p>Для лучшего понимания советую прочитать <a href="/?p=219">этот пост</a>.<br />
<span id="more-313"></span></p>
<p>Решим ту же самую задачу, что и в предыдущем посте, но несколько изменив условия. Итак, вот:</p>
<blockquote><p>1. Главный поток запрашивает у пользователя md5-сумму какого-либо файла и директорию для поиска файла с такой же суммой. Получив эти параметры первый поток запускает 2й и продолжает свою работу (опять ожидает данных от пользователя).<br />
2. Запущенный поток проверяет есть ли в БД приложения файл с таким хешом. Если есть &#8212; выводит результат, иначе, передаёт задания для 3го потока (путь) и по его завершении (задания, не потока ;-) ) заново проверяет БД. Выводит результат.<br />
3. 3й поток хеширует все файлы с помощью md5 по указанному пути и результат записывает в базу данных.</p></blockquote>
<p>Только на этот раз у нас будет действительно один считающий поток, который не будет завершаться, а будет ждать заданий для вычисления.</p>
<p>Модуль для работы с базой данных не изменился (files.py), поменялся только task.py.</p>
<p>Вот логика работы приложения вкратце:</p>
<p>1. В <strong>конструкторе класса</strong> объявляем: очередь заданий taskQueue, замок globalTaskEventLock, событие globalTaskEvent &#8212; событие, которое будет оповещать потоки о том, что хеширование какой-либо папки закончено, массив localTaskEvent &#8212; в нём будут хранится события, которые отвечают за полное выполнение задания какого-то конкретного &#171;ищущего&#187; потока, и counter &#8212; счётчик &#171;ищущих&#187; потоков (за всё время работы приложения).</p>
<p>2. Метод <strong>runMainTask(s_hash, s_folder)</strong> &#8212; в параметрах передаётся искомый хеш и директория, в которой следует искать (при поиске в БД не учитывается). Запускает исполнение метода searchFile в отдельном потоке и увеличивает счётчик &#171;ищущих&#187; потоков на 1.</p>
<p>3. <strong>searchFile(s_hash, s_folder) </strong>- аргументы те же, что и у метода runMainTask. Проверяет наличие в базе данных искомого хеша. Если совпадения найдены &#8212; выводит их в консоль и завершает работу, иначе добавляет указанную директорию и имя потока, в котором выполняется (сам метод) в очередь заданий, так же добавляет в массив localTaskEvents пару thName =&gt; event, где thName &#8212; имя потока, а event &#8212; событие, наступление которого означает полное выполнения задания (вся папка прохеширована). Затем ждёт <del datetime="2008-12-20T20:29:57+00:00">второго пришествия</del> в бесконечном цикле события globalTaskEvents (оповещает о том, что хеши для всех файлов в какой-то папке посчитаны и добавлены в БД). При наступлении события проверяет БД, если совпадения найдены &#8212; выводит результат, удаляет своё событие из массива localTaskEvents и завершается, если нет &#8212; проверяет было ли вычислено его задание, если да &#8212; выводит, что ничего не нашёл, опять удаляет свои данные из localTaskEvent и завершается.</p>
<p>3. <strong>runHashTask()</strong> &#8212; исполняет код в бесконечном цикле. Получает имя потока и папку из очереди заданий. Проверяет в массиве localTaskEvents наличие события для имени потока, которое он получил. Если его нет &#8212; то поток уже завершился и удалил своё событие, тогда пытаемся получить следующее задание. Иначе &#8212; рекурсивно проходим по всем подпапкам и хешируем все файлы. После того, как в какой-то подпапке файлы прохешированы &#8212; результат добавляется в БД и с помощью globalTaskEvent оповещаются об этом все потоки. Когда задание завершено &#8212; пытаемся оповестить поток, чьё задание это было. При переходе к каждой новой подпапке проверяем надо ли считать ещё это задание или лучше перейти к следующему.</p>
<p>Вот, в общем то всё, таков мой план ;-).</p>
<p>Листинг нового класса:</p>
<pre>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;"><span class="co1">#!/usr/local/bin/ python</span>
<span class="co1"># -*- coding: utf-8 -*-</span>

<span class="kw1">import</span> <span class="kw3">threading</span>
<span class="kw1">import</span> <span class="kw3">md5</span>
<span class="kw1">import</span> sqlalchemy
<span class="kw1">import</span> <span class="kw3">os</span>, <span class="kw3">sys</span>
<span class="kw1">import</span> files
<span class="kw1">import</span> <span class="kw3">Queue</span>

<span class="kw1">class</span> MainTask<span class="br0">&#40;</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; <span class="kw1">def</span> <span class="kw4">__init__</span><span class="br0">&#40;</span><span class="kw2">self</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">taskQueue</span> = <span class="kw3">Queue</span>.<span class="kw3">Queue</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="co1"># очередь заданий для вычисляющего потока</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEventLock</span> = <span class="kw3">threading</span>.<span class="me1">Lock</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEvent</span> = <span class="kw3">threading</span>.<span class="me1">Event</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="co1"># оповещение о конце вычислений какого-то задания</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">localTaskEvents</span> = <span class="br0">&#123;</span><span class="br0">&#125;</span> <span class="co1"># оповещение о конце вычислений задания какого-то конкретного потока</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">computingThread</span> = <span class="kw3">threading</span>.<span class="me1">Thread</span><span class="br0">&#40;</span>target=<span class="kw2">self</span>.<span class="me1">runHashTask</span>, name=<span class="st0">&quot;ComputerThread&quot;</span><span class="br0">&#41;</span> <span class="co1"># создаём &quot;вычисляющий поток&quot;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">computingThread</span>.<span class="me1">start</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">counter</span> = 0

&nbsp; &nbsp; <span class="kw1">def</span> runMainTask<span class="br0">&#40;</span><span class="kw2">self</span>, s_hash, s_folder<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; c = <span class="kw2">str</span><span class="br0">&#40;</span><span class="kw2">self</span>.<span class="me1">counter</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; th1 = <span class="kw3">threading</span>.<span class="me1">Thread</span><span class="br0">&#40;</span>target=<span class="kw2">self</span>.<span class="me1">searchFile</span>, name=<span class="st0">&quot;SearherThread #&quot;</span>+c, args=<span class="br0">&#91;</span>s_hash,s_folder<span class="br0">&#93;</span><span class="br0">&#41;</span> <span class="co1"># создаём &quot;ищущий&quot; поток</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[MainThread]: *** starting SearcherThread #%s ***&quot;</span> <span class="sy0">%</span> c
&nbsp; &nbsp; &nbsp; &nbsp; th1.<span class="me1">start</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">counter</span> += 1
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>

&nbsp; &nbsp; <span class="kw1">def</span> searchFile<span class="br0">&#40;</span><span class="kw2">self</span>, s_hash, s_folder<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; thName = <span class="kw3">threading</span>.<span class="me1">currentThread</span><span class="br0">&#40;</span><span class="br0">&#41;</span>.<span class="me1">getName</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; q = <span class="kw2">self</span>.<span class="me1">searchInDB</span><span class="br0">&#40;</span>s_hash<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">&amp;</span>gt<span class="sy0">;</span> 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Found %d files in database:&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span>thName, q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> f <span class="kw1">in</span> q:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: %s&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span>thName, f.<span class="me1">path</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: *** thread finished ***&quot;</span> <span class="sy0">%</span> thName
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Files in database not found, start searching...&quot;</span> <span class="sy0">%</span> thName

&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">localTaskEvents</span><span class="br0">&#91;</span>thName<span class="br0">&#93;</span> = <span class="kw3">threading</span>.<span class="me1">Event</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">taskQueue</span>.<span class="me1">put</span><span class="br0">&#40;</span><span class="br0">&#40;</span>thName, s_folder<span class="br0">&#41;</span><span class="br0">&#41;</span>

&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">while</span> <span class="kw2">True</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEvent</span>.<span class="me1">wait</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Checking database...&quot;</span> <span class="sy0">%</span> thName
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; q = <span class="kw2">self</span>.<span class="me1">searchInDB</span><span class="br0">&#40;</span>s_hash<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">&amp;</span>gt<span class="sy0">;</span> 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Total found %d files:&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span>thName, q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> f <span class="kw1">in</span> q:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: %s&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span>thName, f.<span class="me1">path</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">localTaskEvents</span>.<span class="me1">pop</span><span class="br0">&#40;</span>thName<span class="br0">&#41;</span> <span class="co1"># удаляем события для этого потока</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: *** thread finished ***&quot;</span> <span class="sy0">%</span> thName
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">elif</span> <span class="kw2">self</span>.<span class="me1">localTaskEvents</span><span class="br0">&#91;</span>thName<span class="br0">&#93;</span>.<span class="me1">isSet</span><span class="br0">&#40;</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">localTaskEvents</span>.<span class="me1">pop</span><span class="br0">&#40;</span>thName<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Total found 0 files.&quot;</span> <span class="sy0">%</span> thName
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="co1"># сделано чтобы после одного события не цикл не проходил несколько раз</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEventLock</span>.<span class="me1">acquire</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEventLock</span>.<span class="me1">release</span><span class="br0">&#40;</span><span class="br0">&#41;</span>

&nbsp; &nbsp; <span class="kw1">def</span> searchInDB<span class="br0">&#40;</span><span class="kw2">self</span>, s_hash<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; s = files.<span class="me1">Session</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> s.<span class="me1">query</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span><span class="br0">&#41;</span>.<span class="kw2">filter</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span>.<span class="me1">file_hash</span>==s_hash<span class="br0">&#41;</span>

&nbsp; &nbsp; <span class="kw1">def</span> runHashTask<span class="br0">&#40;</span><span class="kw2">self</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; computerThreadName = <span class="kw3">threading</span>.<span class="me1">currentThread</span><span class="br0">&#40;</span><span class="br0">&#41;</span>.<span class="me1">getName</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">while</span> <span class="kw2">True</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; thName, s_folder = <span class="kw2">self</span>.<span class="me1">taskQueue</span>.<span class="me1">get</span><span class="br0">&#40;</span><span class="br0">&#41;</span>

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="co1"># проверяем надо ли ещё считать это задание</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> <span class="kw2">self</span>.<span class="me1">localTaskEvents</span>.<span class="me1">has_key</span><span class="br0">&#40;</span>thName<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Computing %s's thread task...&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span>computerThreadName, thName<span class="br0">&#41;</span>

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; s = files.<span class="me1">Session</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> root, dirs, fnames <span class="kw1">in</span> <span class="kw3">os</span>.<span class="me1">walk</span><span class="br0">&#40;</span>s_folder<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="co1"># опять проверяем актуальность задания</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> <span class="kw1">not</span> <span class="kw2">self</span>.<span class="me1">localTaskEvents</span>.<span class="me1">has_key</span><span class="br0">&#40;</span>thName<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">break</span>

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; counter = 0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hashes = <span class="br0">&#123;</span><span class="br0">&#125;</span>

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> name <span class="kw1">in</span> fnames:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; q = s.<span class="me1">query</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span><span class="br0">&#41;</span>.<span class="kw2">filter</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span>.<span class="me1">path</span>==<span class="kw2">unicode</span><span class="br0">&#40;</span><span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span>, <span class="st0">&quot;koi8-r&quot;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span> == <span class="nu0">0</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; h = <span class="st0">''</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">try</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f = <span class="kw2">open</span><span class="br0">&#40;</span><span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span>, <span class="st0">&quot;rb&quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">except</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Error: Can't open file %s.&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span>computerThreadName, <span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">else</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; h = <span class="kw3">md5</span>.<span class="kw3">new</span><span class="br0">&#40;</span>f.<span class="me1">read</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>.<span class="me1">hexdigest</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hashes<span class="br0">&#91;</span><span class="kw2">unicode</span><span class="br0">&#40;</span><span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span>, <span class="st0">&quot;koi8-r&quot;</span><span class="br0">&#41;</span><span class="br0">&#93;</span> = h
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; counter += 1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">try</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f.<span class="me1">close</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">except</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">pass</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Hashing %s finished...&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span>computerThreadName, root<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> counter <span class="sy0">!</span>= 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; files.<span class="me1">add_hashes</span><span class="br0">&#40;</span>hashes<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEventLock</span>.<span class="me1">acquire</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEvent</span>.<span class="kw2">set</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEvent</span> = <span class="kw3">threading</span>.<span class="me1">Event</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="co1"># создаём новый экземпляр события</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEventLock</span>.<span class="me1">release</span><span class="br0">&#40;</span><span class="br0">&#41;</span>

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="co1"># try - на случай, если поток уже завершился, удалив задание и событие</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">try</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">localTaskEvents</span><span class="br0">&#91;</span>thName<span class="br0">&#93;</span>.<span class="kw2">set</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">except</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">pass</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="co1"># добавлено на случай, если мы ищем в папке,</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="co1"># все файлы которой уже есть в БД,</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="co1"># иначе globalEvent не наступит ни разу</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEventLock</span>.<span class="me1">acquire</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEvent</span>.<span class="kw2">set</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEvent</span> = <span class="kw3">threading</span>.<span class="me1">Event</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">globalTaskEventLock</span>.<span class="me1">release</span><span class="br0">&#40;</span><span class="br0">&#41;</span>

<span class="kw1">if</span> __name__ == <span class="st0">&quot;__main__&quot;</span>:
&nbsp; &nbsp; mt = MainTask<span class="br0">&#40;</span><span class="br0">&#41;</span>

&nbsp; &nbsp; <span class="co1"># для теста ищем всё в одной папке - 3 файла + 1 несуществующий</span>
&nbsp; &nbsp; hashes = <span class="br0">&#40;</span><span class="st0">&quot;5f4dad3fbd8c6761f8ca218bd6d8e467&quot;</span>, <span class="st0">&quot;500ead228d2ec9af9986a9f10ad13e84&quot;</span>, <span class="st0">&quot;e1d0bced5b03d00b3d09710705eef3b5&quot;</span>, <span class="st0">&quot;invalid hash&quot;</span>, <span class="st0">&quot;2d3153b321a7ce6c2cd56af3231d79a4&quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; folder = <span class="st0">&quot;/usr/home/lizzard/music/music/Roll&quot;</span>

&nbsp; &nbsp; <span class="kw1">for</span> h <span class="kw1">in</span> hashes:
&nbsp; &nbsp; &nbsp; &nbsp; mt.<span class="me1">runMainTask</span><span class="br0">&#40;</span>h, folder<span class="br0">&#41;</span>

&nbsp; &nbsp; <span class="co1"># завершаем программу</span>
&nbsp; &nbsp; <span class="kw1">while</span> <span class="kw3">threading</span>.<span class="me1">activeCount</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">&amp;</span>gt<span class="sy0">;</span> 2:
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">pass</span>
&nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[MainThread]: *** all computes finished ***&quot;</span>
&nbsp; &nbsp; <span class="kw3">sys</span>.<span class="me1">exit</span><span class="br0">&#40;</span><span class="nu0">0</span><span class="br0">&#41;</span></div>
</div>
</pre>
<p>Стоит отметить, что при преобразовании строк в юникод с помощью функции unicode стоит указать кодировку, специфичкескую для Вашей ОС (для windows &#8212; cp1251, freebsd &#8212; koi8-r).</p>
<p>Вот результат выполнения кода:</p>
<div class="codesnip-container" >% python task_1.py<br />
[MainThread]: *** starting SearcherThread #0 ***<br />
[MainThread]: *** starting SearcherThread #1 ***<br />
[SearherThread #0]: Files in database not found, start searching&#8230;<br />
[ComputerThread]: Computing SearherThread #0&#8242;s thread task&#8230;<br />
[SearherThread #1]: Files in database not found, start searching&#8230;<br />
[MainThread]: *** starting SearcherThread #2 ***<br />
[SearherThread #2]: Files in database not found, start searching&#8230;<br />
[MainThread]: *** starting SearcherThread #3 ***<br />
[SearherThread #3]: Files in database not found, start searching&#8230;<br />
[MainThread]: *** starting SearcherThread #4 ***<br />
[SearherThread #4]: Files in database not found, start searching&#8230;<br />
[ComputerThread]: Hashing /usr/home/lizzard/music/music/Roll finished&#8230;<br />
[SearherThread #0]: Checking database&#8230;<br />
[SearherThread #1]: Checking database&#8230;<br />
[SearherThread #2]: Checking database&#8230;<br />
[SearherThread #3]: Checking database&#8230;<br />
[SearherThread #4]: Checking database&#8230;<br />
[SearherThread #1]: Total found 1 files:<br />
[SearherThread #2]: Total found 1 files:<br />
[SearherThread #2]: /usr/home/lizzard/music/music/Roll/in_taberna.mp3<br />
[SearherThread #2]: *** thread finished ***<br />
[SearherThread #1]: /usr/home/lizzard/music/music/Roll/totusfloreo.mp3<br />
[SearherThread #1]: *** thread finished ***<br />
[SearherThread #0]: Total found 1 files:<br />
[SearherThread #0]: /usr/home/lizzard/music/music/Roll/шалом.mp3<br />
[SearherThread #0]: *** thread finished ***<br />
[ComputerThread]: Hashing /usr/home/lizzard/music/music/Roll/Garmarna finished&#8230;<br />
[SearherThread #4]: Checking database&#8230;<br />
[SearherThread #3]: Checking database&#8230;<br />
[SearherThread #4]: Checking database&#8230;<br />
[SearherThread #3]: Checking database&#8230;<br />
[ComputerThread]: Computing SearherThread #3&#8242;s thread task&#8230;<br />
[ComputerThread]: Hashing /usr/home/lizzard/music/music/Roll finished&#8230;<br />
[ComputerThread]: Hashing /usr/home/lizzard/music/music/Roll/Garmarna finished&#8230;<br />
[ComputerThread]: Hashing /usr/home/lizzard/music/music/Roll/Hellawes, The_Mill finished&#8230;<br />
[SearherThread #3]: Checking database&#8230;<br />
[SearherThread #4]: Checking database&#8230;<br />
&#8230;<br />
[ComputerThread]: Hashing /usr/home/lizzard/music/music/Roll/Иллет finished&#8230;<br />
[SearherThread #4]: Checking database&#8230;<br />
[SearherThread #3]: Checking database&#8230;<br />
[SearherThread #4]: Total found 1 files:<br />
[SearherThread #4]: /usr/home/lizzard/music/music/Roll/Иллет/ilet_01.mp3<br />
[SearherThread #4]: *** thread finished ***<br />
[ComputerThread]: Hashing /usr/home/lizzard/music/music/Roll/Крыс и Шмендра finished&#8230;<br />
[SearherThread #3]: Checking database&#8230;<br />
&#8230;<br />
[ComputerThread]: Hashing /usr/home/lizzard/music/music/Roll/Ю. Буркин finished&#8230;<br />
[SearherThread #3]: Checking database&#8230;<br />
[SearherThread #3]: Total found 0 files.<br />
[MainThread]: *** all computes finished ***<br />
%</div>
<p>Конечно, при желании можно улучшить алгоритм, но как пример для изучения потоков в python&#8217;е, имхо, сойдёт ;-).</p>
<p>Исходники к статье в архиве &#8212; <a href="/wp-files/313/threading_v2.tar">тут</a>.</p>
<p>Ссылки к статье:<br />
<a href="http://docs.python.org/library/threading.html">http://docs.python.org/library/threading.html</a> &#8212; официальная документация python по модулю threading (англ.).</p>
]]></content:encoded>
			<wfw:commentRss>http://zetblog.ru/programming/200812/python-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0-%d1%81-%d0%bf%d0%be%d1%82%d0%be%d0%ba%d0%b0%d0%bc%d0%b8-%d1%87%d0%b0%d1%81%d1%82%d1%8c-2/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Python: Работа с потоками.</title>
		<link>http://zetblog.ru/programming/200812/python-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0-%d1%81-%d0%bf%d0%be%d1%82%d0%be%d0%ba%d0%b0%d0%bc%d0%b8/</link>
		<comments>http://zetblog.ru/programming/200812/python-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0-%d1%81-%d0%bf%d0%be%d1%82%d0%be%d0%ba%d0%b0%d0%bc%d0%b8/#comments</comments>
		<pubDate>Wed, 10 Dec 2008 14:52:37 +0000</pubDate>
		<dc:creator>lizz</dc:creator>
				<category><![CDATA[параллельное]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[ORM]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[SQLAlchemy]]></category>
		<category><![CDATA[threading]]></category>

		<guid isPermaLink="false">http://zetblog.ru/?p=219</guid>
		<description><![CDATA[В данной статье будет рассмотрена реализация многопоточного приложения на Python&#8217;е. Сама задачка выглядит примерно так: 1. Главный поток запрашивает у пользователя md5-сумму какого-либо файла и директорию для поиска файла с такой же суммой. Получив эти параметры первый поток запускает 2й и продолжает свою работу (опять ожидает данных от пользователя). 2. Запущенный поток проверяет есть ли [...]]]></description>
			<content:encoded><![CDATA[<p>В данной статье будет рассмотрена реализация многопоточного приложения на Python&#8217;е. Сама задачка выглядит примерно так:<br />
1. Главный поток запрашивает у пользователя md5-сумму какого-либо файла и директорию для поиска файла с такой же суммой. Получив эти параметры первый поток запускает 2й и продолжает свою работу (опять ожидает данных от пользователя).<br />
2. Запущенный поток проверяет есть ли в БД приложения файл с таким хешом. Если есть &#8212; выводит результат, иначе &#8212; запускает 3й поток с такими же параметрами (путь и хеш) и по его завершении заново проверяет БД. Выводит результат.<br />
3. 3й поток хеширует все файлы с помощью md5 по указанному пути и результат записывает в базу данных.<br />
<span id="more-219"></span></p>
<p>Главный поток у нас всегда один, вторых потоков может быть сколько угодно, а третьих (вычисляющих) &#8212; только один запущенный.</p>
<p>Для наглядности реализуем всё сначала без потоков. Создадим класс MainTask со следующими методами:</p>
<ul>
<li>runMainTask(self, sHash, sFolder)</li>
<li>searchFile(self, s_hash, s_folder)</li>
<li>runHashTask(self, s_folder, s_hash)</li>
</ul>
<p><strong>runMainTask</strong> &#8212; в общем то почти ничего не делает ;-).  Запускает метод searchFile с соответствующими параметрами.</p>
<p><strong>searchFile</strong> &#8212; проверяет БД на наличие искомого хеша, если записи нашлись &#8212; выводит их и завершается. Если записи не найдены &#8212; вызывает метод runHashTask, затем опять проверяет БД.</p>
<p><strong>runHashTask</strong> &#8212; рекурсивно (т.е. включая подкаталоги) обходит переданный через параметры путь и хеширует файлы. Если во время обхода встречается искомый хеш &#8212; добавляет результат вычислений (все вычисленные хеши для файлов) в БД и завершается. Если хеш не был найден &#8212; делает то же самое (обновляет БД и завершает работу) ;-).</p>
<p>Для хранения результатов вычисления была выбрана база данных, чтобы не использовать какой-либо сервер был выбран SQLite (СУБД работает без сервера на основе файлов).</p>
<p>Для нашего приложения нужна всего одна табличка с двумя полями &#8212; &#171;path&#187; и &#171;file_hash&#187;. Саму таблицу назовём &#171;hashes&#187;. Поле &#171;path&#187; будет содержать полный путь к файлу включая его имя, а &#171;file_hash&#187; &#8212; хеш этого файла (неожиданный поворот событий? ;-) ).</p>
<p>Так же сразу я решил описать функцию add_hashes. Получает в качестве параметра словарь вида ["file0":"hash0", "file1":"hash1", ...] и пытается добавить его в БД.</p>
<p>Смотрим, что у нас получилось. Модуль для работы с базой данных (files.py):</p>
<pre>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;"><span class="kw1">import</span> sqlalchemy <span class="kw1">as</span> sa
<span class="kw1">from</span> sqlalchemy.<span class="me1">orm</span> <span class="kw1">import</span> mapper, sessionmaker

engine = sa.<span class="me1">create_engine</span><span class="br0">&#40;</span><span class="st0">&quot;sqlite:///hashes.db&quot;</span>, echo=<span class="kw2">False</span><span class="br0">&#41;</span>
metadata = sa.<span class="me1">MetaData</span><span class="br0">&#40;</span><span class="br0">&#41;</span>

hashes_table = sa.<span class="me1">Table</span><span class="br0">&#40;</span><span class="st0">&quot;hashes&quot;</span>, metadata,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sa.<span class="me1">Column</span><span class="br0">&#40;</span><span class="st0">&quot;path&quot;</span>, sa.<span class="me1">Text</span><span class="br0">&#40;</span>convert_unicode=<span class="kw2">True</span><span class="br0">&#41;</span>, nullable=<span class="kw2">False</span>, primary_key=<span class="kw2">True</span><span class="br0">&#41;</span>,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sa.<span class="me1">Column</span><span class="br0">&#40;</span><span class="st0">&quot;file_hash&quot;</span>, sa.<span class="me1">String</span><span class="br0">&#40;</span>32, convert_unicode=<span class="kw2">True</span><span class="br0">&#41;</span>, nullable=<span class="kw2">False</span><span class="br0">&#41;</span>,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="br0">&#41;</span>

metadata.<span class="me1">create_all</span><span class="br0">&#40;</span>engine<span class="br0">&#41;</span>

<span class="kw1">class</span> Hash<span class="br0">&#40;</span><span class="kw2">object</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; <span class="kw1">def</span> <span class="kw4">__init__</span><span class="br0">&#40;</span><span class="kw2">self</span>, path=<span class="st0">''</span>, file_hash=<span class="st0">''</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">path</span> &nbsp; &nbsp; &nbsp;= path
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">file_hash</span> = file_hash
&nbsp; &nbsp; <span class="kw1">def</span> <span class="kw4">__repr__</span><span class="br0">&#40;</span><span class="kw2">self</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="st0">&quot;&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span><span class="kw2">self</span>.<span class="me1">path</span>, <span class="kw2">self</span>.<span class="me1">file_hash</span><span class="br0">&#41;</span>

<span class="kw1">def</span> add_hashes<span class="br0">&#40;</span>files<span class="br0">&#41;</span>:
&nbsp; &nbsp; session = Session<span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; counter = 0
&nbsp; &nbsp; <span class="kw1">try</span>:
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> f <span class="kw1">in</span> files:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; session.<span class="me1">save_or_update</span><span class="br0">&#40;</span>Hash<span class="br0">&#40;</span>f, files<span class="br0">&#91;</span>f<span class="br0">&#93;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; counter += 1
&nbsp; &nbsp; &nbsp; &nbsp; session.<span class="me1">commit</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; <span class="kw1">except</span>:
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;Error. Can't add hashes into database.&quot;</span>
&nbsp; &nbsp; <span class="kw1">finally</span>:
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> counter

mapper<span class="br0">&#40;</span>Hash, hashes_table<span class="br0">&#41;</span>
Session = sessionmaker<span class="br0">&#40;</span>bind = engine<span class="br0">&#41;</span></div>
</div>
</pre>
<p>Основной модуль (task_1.py):</p>
<pre>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;"><span class="kw1">import</span> <span class="kw3">md5</span>
<span class="kw1">import</span> <span class="kw3">os</span>, <span class="kw3">sys</span>
<span class="kw1">import</span> files

<span class="kw1">class</span> MainTask<span class="br0">&#40;</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; <span class="kw1">def</span> <span class="kw4">__init__</span><span class="br0">&#40;</span><span class="kw2">self</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">sHash</span> = <span class="st0">&quot;&quot;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">sFolder</span> = <span class="st0">&quot;&quot;</span>

&nbsp; &nbsp; <span class="kw1">def</span> runMainTask<span class="br0">&#40;</span><span class="kw2">self</span>, sHash, sFolder<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">sHash</span> = sHash
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">sFolder</span> = sFolder
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">searchFile</span><span class="br0">&#40;</span><span class="kw2">self</span>.<span class="me1">sHash</span>, <span class="kw2">self</span>.<span class="me1">sFolder</span><span class="br0">&#41;</span>

&nbsp; &nbsp; <span class="kw1">def</span> searchFile<span class="br0">&#40;</span><span class="kw2">self</span>, s_hash, s_folder<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; s = files.<span class="me1">Session</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; q = s.<span class="me1">query</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span><span class="br0">&#41;</span>.<span class="kw2">filter</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span>.<span class="me1">file_hash</span>==s_hash<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">&amp;</span>gt<span class="sy0">;</span> 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;Found files in database:&quot;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> f <span class="kw1">in</span> q:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> f.<span class="me1">path</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">else</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;Files in database not found, searching...&quot;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">runHashTask</span><span class="br0">&#40;</span>s_folder, s_hash<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; q = s.<span class="me1">query</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span><span class="br0">&#41;</span>.<span class="kw2">filter</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span>.<span class="me1">file_hash</span>==s_hash<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">&amp;</span>gt<span class="sy0">;</span> 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;Total found %d files:&quot;</span> <span class="sy0">%</span> q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> f <span class="kw1">in</span> q:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> f.<span class="me1">path</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">else</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;File with hash %s not found.&quot;</span> <span class="sy0">%</span> s_hash
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>

&nbsp; &nbsp; <span class="kw1">def</span> runHashTask<span class="br0">&#40;</span><span class="kw2">self</span>, s_folder, s_hash<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; hashes = <span class="br0">&#123;</span><span class="br0">&#125;</span>
&nbsp; &nbsp; &nbsp; &nbsp; s = files.<span class="me1">Session</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> root, dirs, fnames <span class="kw1">in</span> <span class="kw3">os</span>.<span class="me1">walk</span><span class="br0">&#40;</span>s_folder<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> name <span class="kw1">in</span> fnames:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; q = s.<span class="me1">query</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span><span class="br0">&#41;</span>.<span class="kw2">filter</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span>.<span class="me1">path</span>==<span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span> == <span class="nu0">0</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; h = <span class="st0">''</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">try</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f = <span class="kw2">open</span><span class="br0">&#40;</span><span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span>, <span class="st0">&quot;rb&quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">except</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;Error: Can't open file %s.&quot;</span> <span class="sy0">%</span> <span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">else</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; h = <span class="kw3">md5</span>.<span class="kw3">new</span><span class="br0">&#40;</span>f.<span class="me1">read</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>.<span class="me1">hexdigest</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hashes<span class="br0">&#91;</span><span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span><span class="br0">&#93;</span> = h
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">try</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f.<span class="me1">close</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">except</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">pass</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> h == s_hash:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; files.<span class="me1">add_hashes</span><span class="br0">&#40;</span>hashes<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>
&nbsp; &nbsp; &nbsp; &nbsp; files.<span class="me1">add_hashes</span><span class="br0">&#40;</span>hashes<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>

<span class="kw1">if</span> __name__ == <span class="st0">&quot;__main__&quot;</span>:
&nbsp; &nbsp; mt = MainTask<span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; <span class="kw1">while</span> <span class="kw2">True</span>:
&nbsp; &nbsp; &nbsp; &nbsp; shash &nbsp; = <span class="kw2">unicode</span><span class="br0">&#40;</span><span class="kw2">raw_input</span><span class="br0">&#40;</span><span class="st0">&quot;Enter file hash: &quot;</span><span class="br0">&#41;</span>, <span class="st0">&quot;cp1251&quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; sfolder = <span class="kw2">unicode</span><span class="br0">&#40;</span><span class="kw2">raw_input</span><span class="br0">&#40;</span><span class="st0">&quot;Enter folder to search: &quot;</span><span class="br0">&#41;</span>, <span class="st0">&quot;cp1251&quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> shash == <span class="st0">&quot;&quot;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">raw_input</span><span class="br0">&#40;</span><span class="st0">&quot;Press Enter to exit...&quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">sys</span>.<span class="me1">exit</span><span class="br0">&#40;</span><span class="nu0">0</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; mt.<span class="me1">runMainTask</span><span class="br0">&#40;</span>shash, sfolder<span class="br0">&#41;</span></div>
</div>
</pre>
<p>Теперь попробуем сделать всё то же самое, но используя потоки. Модуль для работы с БД менять не надо, все изменения коснутся только основного модуля.</p>
<p>runMainTask теперь должен вызывать исполнение метода searchFile в отдельном потоке (так же, как и searchFile должен вызывать runHashTask). Перед вызовом третьего потока мы используем блокировку (threading.Lock()), что не позволяет одновременно нескольким вторым потокам запустить третий.</p>
<p>Способ использования блокировок:</p>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;">lock = <span class="kw3">threading</span>.<span class="me1">Lock</span><span class="br0">&#40;</span><span class="br0">&#41;</span><br />
<span class="co1"># Перед участком кода, который нежелательно чтобы выполнялся</span><br />
<span class="co1"># несколькими потоками одновременно запираем замок:</span><br />
lock.<span class="me1">acquire</span><span class="br0">&#40;</span><span class="br0">&#41;</span><br />
<span class="co1"># &#8230; какой-то код</span><br />
<span class="co1"># затем отпираем замок:</span><br />
lock.<span class="me1">release</span><span class="br0">&#40;</span><span class="br0">&#41;</span></div>
</div>
<p>Можно так же использовать один и тот же экземпляр замка в разных местах, если надо чтобы эти участки кода не выполнялись одновременно.</p>
<p>Порождение дочернего потока происходит с помощью класса Thread в модуле threading.</p>
<p>Описание использованных параметров класса Thread:</p>
<ul>
<li>target &#8212; указывает на имя функции (в данном случае метод класса), которую стоит выполнять в новом потоке</li>
<li>name &#8212; имя порождаемого потока</li>
<li>args &#8212; список аргументов, которые мы передаём в функцию, указанную в параметре target</li>
</ul>
<p>Поток создаётся так:</p>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;">th = <span class="kw3">threading</span>.<span class="me1">Thread</span><span class="br0">&#40;</span>&#8230;<span class="br0">&#41;</span></div>
</div>
<p>Запускается на исполнение поток путём выозова метода start:</p>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;">th = <span class="kw3">threading</span>.<span class="me1">start</span><span class="br0">&#40;</span><span class="br0">&#41;</span></div>
</div>
<p>Метод join() ожидает завершения потока.</p>
<p>Ну, вроде бы теперь должно быть всё понятно. Посмотрим, что у нас должно получить в итоге:</p>
<pre>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;"><span class="co1"># -*- coding: cp1251 -*-</span>
<span class="kw1">import</span> <span class="kw3">threading</span>
<span class="kw1">import</span> <span class="kw3">md5</span>
<span class="kw1">import</span> sqlalchemy
<span class="kw1">import</span> <span class="kw3">os</span>, <span class="kw3">sys</span>
<span class="kw1">import</span> files

<span class="kw1">class</span> MainTask<span class="br0">&#40;</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; <span class="kw1">def</span> <span class="kw4">__init__</span><span class="br0">&#40;</span><span class="kw2">self</span><span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">sHash</span> = <span class="st0">&quot;&quot;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">sFolder</span> = <span class="st0">&quot;&quot;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.__th2Lock = <span class="kw3">threading</span>.<span class="me1">Lock</span><span class="br0">&#40;</span><span class="br0">&#41;</span>

&nbsp; &nbsp; <span class="kw1">def</span> runMainTask<span class="br0">&#40;</span><span class="kw2">self</span>, sHash, sFolder, c=0<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; c = <span class="kw2">str</span><span class="br0">&#40;</span>c<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">sHash</span> = sHash
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.<span class="me1">sFolder</span> = sFolder
&nbsp; &nbsp; &nbsp; &nbsp; <span class="co1">#raw_input(&quot;1&quot;)</span>
&nbsp; &nbsp; &nbsp; &nbsp; th1 = <span class="kw3">threading</span>.<span class="me1">Thread</span><span class="br0">&#40;</span>target=<span class="kw2">self</span>.<span class="me1">searchFile</span>, name=<span class="st0">&quot;th1_&quot;</span>+c, args=<span class="br0">&#91;</span><span class="kw2">self</span>.<span class="me1">sHash</span>,<span class="kw2">self</span>.<span class="me1">sFolder</span>,<span class="st0">&quot;th1_&quot;</span>+c<span class="br0">&#93;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="co1">#raw_input(&quot;1&quot;)</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[MainThred]: *** starting th1_%s ***&quot;</span> <span class="sy0">%</span> c
&nbsp; &nbsp; &nbsp; &nbsp; th1.<span class="me1">start</span><span class="br0">&#40;</span><span class="br0">&#41;</span>

&nbsp; &nbsp; <span class="kw1">def</span> searchFile<span class="br0">&#40;</span><span class="kw2">self</span>, s_hash, s_folder, thName<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; s = files.<span class="me1">Session</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; q = s.<span class="me1">query</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span><span class="br0">&#41;</span>.<span class="kw2">filter</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span>.<span class="me1">file_hash</span>==s_hash<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">&amp;</span>gt<span class="sy0">;</span> 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Found %d files in database:&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span>thName, q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> f <span class="kw1">in</span> q:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: %s&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span>thName, f.<span class="me1">path</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: *** thread finished ***&quot;</span> <span class="sy0">%</span> thName
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Files in database not found, searching...&quot;</span> <span class="sy0">%</span> thName
&nbsp; &nbsp; &nbsp; &nbsp; <span class="co1">### START LOCK</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.__th2Lock.<span class="me1">acquire</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; th2 = <span class="kw3">threading</span>.<span class="me1">Thread</span><span class="br0">&#40;</span>target=<span class="kw2">self</span>.<span class="me1">runHashTask</span>, name=<span class="st0">&quot;th2&quot;</span>, args=<span class="br0">&#91;</span>s_folder,s_hash<span class="br0">&#93;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: *** starting th2 ***&quot;</span> <span class="sy0">%</span> thName
&nbsp; &nbsp; &nbsp; &nbsp; th2.<span class="me1">start</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; th2.<span class="me1">join</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: *** th2 finished ***&quot;</span> <span class="sy0">%</span> thName
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">self</span>.__th2Lock.<span class="me1">release</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="co1">### END LOCK</span>
&nbsp; &nbsp; &nbsp; &nbsp; q = s.<span class="me1">query</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span><span class="br0">&#41;</span>.<span class="kw2">filter</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span>.<span class="me1">file_hash</span>==s_hash<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">&amp;</span>gt<span class="sy0">;</span> 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: Total found %d files:&quot;</span> <span class="sy0">%</span> <span class="br0">&#40;</span>thName, q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> f <span class="kw1">in</span> q:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> f.<span class="me1">path</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">else</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: File with hash %s not found.&quot;</span> &nbsp;<span class="sy0">%</span> <span class="br0">&#40;</span>thName, s_hash<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[%s]: *** thread finished ***&quot;</span> <span class="sy0">%</span> thName
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>

&nbsp; &nbsp; <span class="kw1">def</span> runHashTask<span class="br0">&#40;</span><span class="kw2">self</span>, s_folder, s_hash<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; hashes = <span class="br0">&#123;</span><span class="br0">&#125;</span>
&nbsp; &nbsp; &nbsp; &nbsp; s = files.<span class="me1">Session</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> root, dirs, fnames <span class="kw1">in</span> <span class="kw3">os</span>.<span class="me1">walk</span><span class="br0">&#40;</span>s_folder<span class="br0">&#41;</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> name <span class="kw1">in</span> fnames:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; q = s.<span class="me1">query</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span><span class="br0">&#41;</span>.<span class="kw2">filter</span><span class="br0">&#40;</span>files.<span class="me1">Hash</span>.<span class="me1">path</span>==<span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> q.<span class="me1">count</span><span class="br0">&#40;</span><span class="br0">&#41;</span> == <span class="nu0">0</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; h = <span class="st0">''</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">try</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f = <span class="kw2">open</span><span class="br0">&#40;</span><span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span>, <span class="st0">&quot;rb&quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">except</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">print</span> <span class="st0">&quot;[th2]: Error: Can't open file %s.&quot;</span> <span class="sy0">%</span> <span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">else</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; h = <span class="kw3">md5</span>.<span class="kw3">new</span><span class="br0">&#40;</span>f.<span class="me1">read</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>.<span class="me1">hexdigest</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hashes<span class="br0">&#91;</span><span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">join</span><span class="br0">&#40;</span>root, name<span class="br0">&#41;</span><span class="br0">&#93;</span> = h
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">try</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f.<span class="me1">close</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">except</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">pass</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> h == s_hash:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; files.<span class="me1">add_hashes</span><span class="br0">&#40;</span>hashes<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>
&nbsp; &nbsp; &nbsp; &nbsp; files.<span class="me1">add_hashes</span><span class="br0">&#40;</span>hashes<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="kw2">True</span>

<span class="kw1">if</span> __name__ == <span class="st0">&quot;__main__&quot;</span>:
&nbsp; &nbsp; mt = MainTask<span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; counter = 0
&nbsp; &nbsp; <span class="kw1">while</span> <span class="kw2">True</span>:
&nbsp; &nbsp; &nbsp; &nbsp; shash &nbsp; = <span class="kw2">unicode</span><span class="br0">&#40;</span><span class="kw2">raw_input</span><span class="br0">&#40;</span><span class="st0">&quot;[MainThread]: Enter file hash: &quot;</span><span class="br0">&#41;</span>, <span class="st0">&quot;cp1251&quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; sfolder = <span class="kw2">raw_input</span><span class="br0">&#40;</span><span class="st0">&quot;[MainThread]: Enter folder to search: &quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> shash == <span class="st0">''</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">raw_input</span><span class="br0">&#40;</span><span class="st0">&quot;[MainThread]: Press Enter to exit...&quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">sys</span>.<span class="me1">exit</span><span class="br0">&#40;</span>0<span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> sfolder == <span class="st0">''</span>:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sfolder = <span class="st0">'.'</span>
&nbsp; &nbsp; &nbsp; &nbsp; sfolder = <span class="kw2">unicode</span><span class="br0">&#40;</span><span class="kw3">os</span>.<span class="me1">path</span>.<span class="me1">abspath</span><span class="br0">&#40;</span>sfolder<span class="br0">&#41;</span>, <span class="st0">&quot;cp1251&quot;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; mt.<span class="me1">runMainTask</span><span class="br0">&#40;</span>shash, sfolder, <span class="kw2">str</span><span class="br0">&#40;</span>counter<span class="br0">&#41;</span><span class="br0">&#41;</span>
&nbsp; &nbsp; &nbsp; &nbsp; counter += <span class="nu0">1</span></div>
</div>
</pre>
<p>Вывод потоков может быть перемешан, но это сделано для наглядности.</p>
<p>Стоит так же отметить, что преобразование пути в кодировку utf8 вызвано проблемами с SQLAlchemy, как то он странно обрабатывает кодировку cp1251.</p>
<p>Если кто-то не знает как получить md5-хеш файла, вот пример:</p>
<pre>
<div class="codesnip-container" >
<div class="python codesnip" style="font-family:monospace;"><span class="kw1">import</span> <span class="kw3">md5</span>

f = <span class="kw2">open</span><span class="br0">&#40;</span><span class="st0">&quot;path_to_file&quot;</span>, <span class="st0">&quot;rb&quot;</span><span class="br0">&#41;</span>
<span class="kw2">hash</span> = <span class="kw3">md5</span>.<span class="kw3">new</span><span class="br0">&#40;</span>f.<span class="me1">read</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>.<span class="me1">hexdigest</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
f.<span class="me1">close</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
<span class="kw1">print</span> <span class="kw2">hash</span></div>
</div>
</pre>
<p>Архив с исходниками <a href="http://zetblog.ru/wp-files/219/threading.rar">тут</a>. В архиве в корне &#8212; программа с использованием потоков, в папке line &#8212; без.</p>
<p>Ссылки к статье:<br />
<a href="http://www.intuit.ru/department/pl/python/class/free/11/">http://www.intuit.ru/department/pl/python/class/free/11/</a> &#8212; &#171;Многопоточные вычисления&#187;.<br />
<a href="http://keysolutions.ru/articles/osnovy-raboty-s-potokami-v-python">http://keysolutions.ru/articles/osnovy-raboty-s-potokami-v-python</a> &#8212; Сергей Шилов &#8212; &#171;Основы работы с потоками в Python&#187;.</p>
]]></content:encoded>
			<wfw:commentRss>http://zetblog.ru/programming/200812/python-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0-%d1%81-%d0%bf%d0%be%d1%82%d0%be%d0%ba%d0%b0%d0%bc%d0%b8/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Python: Бесплатная отправка SMS через mail.ru.</title>
		<link>http://zetblog.ru/programming/200811/python-%d0%b1%d0%b5%d1%81%d0%bf%d0%bb%d0%b0%d1%82%d0%bd%d0%b0%d1%8f-%d0%be%d1%82%d0%bf%d1%80%d0%b0%d0%b2%d0%ba%d0%b0-sms-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-mailru/</link>
		<comments>http://zetblog.ru/programming/200811/python-%d0%b1%d0%b5%d1%81%d0%bf%d0%bb%d0%b0%d1%82%d0%bd%d0%b0%d1%8f-%d0%be%d1%82%d0%bf%d1%80%d0%b0%d0%b2%d0%ba%d0%b0-sms-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-mailru/#comments</comments>
		<pubDate>Tue, 18 Nov 2008 09:29:10 +0000</pubDate>
		<dc:creator>lizz</dc:creator>
				<category><![CDATA[Программирование]]></category>
		<category><![CDATA[сетевое]]></category>
		<category><![CDATA[Internet]]></category>
		<category><![CDATA[mail.ru]]></category>
		<category><![CDATA[ORM]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[SMS]]></category>
		<category><![CDATA[SQLAlchemy]]></category>

		<guid isPermaLink="false">http://zetblog.ru/?p=15</guid>
		<description><![CDATA[В mail.ru агенте есть возможно одна полезная вещь - отправка до 50 sms в сутки с одного аккаунта. Протокол агента является открытым и его можно посмотреть на <a href="http://agent.mail.ru/ru/developers/protocol.html">сайте</a>, однако выложена не совсем свежая версия и не сказано ни слова об отправке sms, но это не проблема.]]></description>
			<content:encoded><![CDATA[<p>В mail.ru агенте есть возможно одна полезная вещь &#8212; отправка до 50 sms в сутки с одного аккаунта. Протокол агента является открытым и его можно посмотреть на <a href="http://agent.mail.ru/ru/developers/protocol.html">сайте</a>, однако выложена не совсем свежая версия и не сказано ни слова об отправке sms, но это не проблема.<br />
<span id="more-15"></span></p>
<p>Итак, смотрим описание протокола. Для начала нам надо подключится к mrim.mail.ru на порт 2042 или 443 и адрес сервера агента и порт в обычном текстовом формате &#171;ip:port&#187;. Затем надо отправить пакет MRIM_CS_HELLO. Как сказано в описании, заголовок пакета имеет следующий формат:</p>
<pre class="brush: cpp">
u_long	magic;
u_long	proto;
u_long	seq;
u_long	msg;
u_long	dlen;
u_long	from;
u_long	fromport;
u_char	reserved[16];
</pre>
<p>Размер u_long&#8217;а &#8212; 4 байта. Первое значение magic соответствует числу 0xDEADBEEF &#8212; своеобразный юмор разработчиков, если смотреть на это значение как на строку, то перевод будет что-то вроде &#171;мёртвая говядина&#187;. Однако разработчики mail.ru пошли дальше, что бы нас ещё больше рассмешить они решили отправлять все числовые значения задом наперёд, т.е. нам надо записать в заголовок не 0xDEADBEEF, а 0xEFBEADDE (пишем последний байт &#8212; EF, предпоследний &#8212; BE и т.д.). Строковый переменные отправляются в нормальном порядке. Всё это можно узнать запустив какой-нибудь снифер и посмотрев, что шлёт официальный агент.<br />
В итоге, что мы должны послать на сервер:</p>
<pre class="brush: cpp">magic = 0xEFBEADDE (соответствует 0xDEADBEEF)
proto = 0x07000100 (первые два байта - старшая версия протокола, вторые - младшая, 0x00010007)
seq = 0x01000000 (соответствует 0x000001)
msg = 0x01100000 (MRIM_CS_HELLO - 0x00001001)
dlen = 0x00000000 (длинна данных)
from = 0x00000000 (ip адрес клиента, указывать не обязательно)
fromport = 0x000000 (порт клиента, указывать не обязательно)
reserved[16] = 0x00000000000000000000000000000000 (зарезервировано)</pre>
<p>В ответ мы должны получить пакет с тем же номером последовательности (seq) и типом (msg) MRIM_CS_HELLO_ACK.</p>
<p>Оформим всё это в виде кода. Создаём файл packets.py в котором будут храниться определённые протоколом константы и класс packet с методами:</p>
<ul>
<li>setHost(host, port) для установки адреса и порта с которого происходит подключение к серверу (этот метод не очень то нужен);</li>
<li>getPacket(msg) &#8212; устанавливает тип пакета равный MSG, увеличивает текущий номер последовательности на 1 и возвращает готовый пакет для отправки в текстовом формате;</li>
<li>setPacket(p) &#8212; метод устанавливает из полученного пакета все свойства в классе, если в p содержится поле данных, то оно записывается в свойство data;</li>
<li>addRawData(data) добавляет к свойству data переданный параметр data и обновляет значение длины данных;</li>
<li>addLPSData(data) &#8212; добавляет к свойству data длину прикрепляемых данных (в обратном порядке, как и все числовые значения в протоколе) и сами данные;</li>
<li>addULData(data) &#8212; прикрепляет &#171;цифровые&#187; данные к пакету в обратном порядке;</li>
<li>clear() &#8212; сбрасывает свойства data, dlen, msg;</li>
<li>__d2s(d) &#8212; переводит число d в строку для передачи через сокеты на сервер;</li>
<li>__s2d(s) &#8212; обратный метода для __d2s(d);</li>
</ul>
<p>Вот как всё это безобразие выглядит:</p>
<pre class="brush: python">""" MRIM constants """
PROTO_VERSION_MAJOR             = 1
PROTO_VERSION_MINOR             = 12 #7
PROTO_VERSION                   = (PROTO_VERSION_MAJOR &lt;&lt; 16) | PROTO_VERSION_MINOR
CS_MAGIC                        = 0xDEADBEEF

MRIM_CS_HELLO                   = 0x1001  # C -&gt; S
MRIM_CS_HELLO_ACK               = 0x1002  # S -&gt; C
MRIM_CS_LOGIN_ACK               = 0x1004  # S -&gt; C
MRIM_CS_LOGIN_REJ               = 0x1005  # S -&gt; C
MRIM_CS_PING                    = 0x1006  # C -&gt; S
MRIM_CS_MESSAGE                 = 0x1008  # C -&gt; S
MESSAGE_FLAG_OFFLINE            = 0x00000001
MESSAGE_FLAG_NORECV             = 0x00000004
MESSAGE_FLAG_AUTHORIZE          = 0x00000008  # X-MRIM-Flags: 00000008
MESSAGE_FLAG_SYSTEM             = 0x00000040
MESSAGE_FLAG_RTF                = 0x00000080
MESSAGE_FLAG_CONTACT            = 0x00000200
MESSAGE_FLAG_NOTIFY             = 0x00000400
MESSAGE_FLAG_MULTICAST          = 0x00001000
MAX_MULTICAST_RECIPIENTS        = 50
MESSAGE_USERFLAGS_MASK          = 0x000036A8  # Flags that user is allowed to set himself
MRIM_CS_MESSAGE_ACK             = 0x1009  # S -&gt; C
MRIM_CS_MESSAGE_RECV            = 0x1011  # C -&gt; S
MRIM_CS_MESSAGE_STATUS          = 0x1012  # S -&gt; C
MESSAGE_DELIVERED               = 0x0000  # Message delivered directly to user
MESSAGE_REJECTED_NOUSER         = 0x8001  # Message rejected - no such user
MESSAGE_REJECTED_INTERR         = 0x8003  # Internal server error
MESSAGE_REJECTED_LIMIT_EXCEEDED = 0x8004  # Offline messages limit exceeded
MESSAGE_REJECTED_TOO_LARGE      = 0x8005  # Message is too large
MESSAGE_REJECTED_DENY_OFFMSG    = 0x8006  # User does not accept offline messages
MRIM_CS_USER_STATUS             = 0x100F  # S -&gt; C
STATUS_OFFLINE                  = 0x00000000
STATUS_ONLINE                   = 0x00000001
STATUS_AWAY                     = 0x00000002
STATUS_UNDETERMINATED           = 0x00000003
STATUS_FLAG_INVISIBLE           = 0x80000000
MRIM_CS_LOGOUT                  = 0x1013  # S -&gt; C
LOGOUT_NO_RELOGIN_FLAG          = 0x0010  # Logout due to double login
MRIM_CS_CONNECTION_PARAMS       = 0x1014  # S -&gt; C
MRIM_CS_USER_INFO               = 0x1015  # S -&gt; C
MRIM_CS_ADD_CONTACT             = 0x1019  # C -&gt; S
CONTACT_FLAG_REMOVED            = 0x00000001
CONTACT_FLAG_GROUP              = 0x00000002
CONTACT_FLAG_INVISIBLE          = 0x00000004
CONTACT_FLAG_VISIBLE            = 0x00000008
CONTACT_FLAG_IGNORE             = 0x00000010
CONTACT_FLAG_SHADOW             = 0x00000020
MRIM_CS_ADD_CONTACT_ACK         = 0x101A  # S -&gt; C
CONTACT_OPER_SUCCESS            = 0x0000
CONTACT_OPER_ERROR              = 0x0001
CONTACT_OPER_INTERR             = 0x0002
CONTACT_OPER_NO_SUCH_USER       = 0x0003
CONTACT_OPER_INVALID_INFO       = 0x0004
CONTACT_OPER_USER_EXISTS        = 0x0005
CONTACT_OPER_GROUP_LIMIT        = 0x6
MRIM_CS_MODIFY_CONTACT          = 0x101B  # C -&gt; S
MRIM_CS_MODIFY_CONTACT_ACK      = 0x101C  # S -&gt; C
MRIM_CS_OFFLINE_MESSAGE_ACK     = 0x101D  # S -&gt; C
MRIM_CS_DELETE_OFFLINE_MESSAGE  = 0x101E  # C -&gt; S
MRIM_CS_AUTHORIZE               = 0x1020  # C -&gt; S
MRIM_CS_AUTHORIZE_ACK           = 0x1021  # S -&gt; C
MRIM_CS_CHANGE_STATUS           = 0x1022  # C -&gt; S
MRIM_CS_GET_MPOP_SESSION        = 0x1024  # C -&gt; S
MRIM_CS_MPOP_SESSION            = 0x1025  # S -&gt; C
MRIM_GET_SESSION_FAIL           = 0
MRIM_GET_SESSION_SUCCESS        = 1
MRIM_CS_WP_REQUEST              = 0x1029  # C-&gt;S
PARAMS_NUMBER_LIMIT             = 50
PARAM_VALUE_LENGTH_LIMIT        = 64
MRIM_CS_ANKETA_INFO                = 0x1028  # S-&gt;C
MRIM_ANKETA_INFO_STATUS_OK         = 1
MRIM_ANKETA_INFO_STATUS_NOUSER     = 0
MRIM_ANKETA_INFO_STATUS_DBERR      = 2
MRIM_ANKETA_INFO_STATUS_RATELIMERR = 3
MRIM_CS_MAILBOX_STATUS             = 0x1033
MRIM_CS_CONTACT_LIST2              = 0x1037  # S-&gt;C
GET_CONTACTS_OK                    = 0x0000
GET_CONTACTS_ERROR                 = 0x0001
GET_CONTACTS_INTERR                = 0x0002
CONTACT_INTFLAG_NOT_AUTHORIZED     = 0x0001
MRIM_CS_LOGIN2                     = 0x1038  # C -&gt; S
MAX_CLIENT_DESCRIPTION             = 256

class packet:
  """ MRIM handler """
  magic     = CS_MAGIC
  proto     = PROTO_VERSION
  seq       = 0x00000000
  msg       = 0x00000000
  dlen      = 0x00000000
  from_addr = 0x00000000
  from_port = 0x00000000
  reserved  = 0x00000000
  data      = ""
  size      = 44

  def __init__(self):
    self.reserved  = self.__d2s(0x00000000, 16)  # 16 байт

  def setHost(self, host, port):
    self.from_host, self.from_port = host, port
    return True

  def getPacket(self, msg):
    self.seq += 1
    self.msg  = msg
    self.dlen = len(self.data)
    return self.__d2s(self.magic, 4) + self.__d2s(self.proto, 4) + \
      self.__d2s(self.seq, 4) + self.__d2s(self.msg, 4) + \
      self.__d2s(self.dlen, 4) + self.__d2s(self.from_addr, 4) + \
      self.__d2s(self.from_port, 4) + self.reserved + self.data

  def setPacket(self, p):
    if len(p) &lt; 44:
      return False
    self.t = p
    self.magic     = self.__s2d(p[   :  4])
    self.proto     = self.__s2d(p[4  :  8])
    self.seq       = self.__s2d(p[8  : 12])
    self.msg       = self.__s2d(p[12 : 16])
    self.dlen      = self.__s2d(p[16 : 20])
    self.from_addr = self.__s2d(p[20 : 24])
    self.from_port = self.__s2d(p[24 : 28])
    self.reserved  = self.__s2d(p[28 : 44])
    if len(p[44 : ]):
      self.data    = self.__s2d(p[44 : ])
    else:
      self.data    = ""
    return True

  def addRawData(self, data):
    self.data += data
    self.dlen = len(self.data)
    return True

  def addLPSData(self, data):
    dlen = len(data)
    self.data += self.__d2s(dlen, 4)
    self.data += data
    self.dlen = len(self.data)
    return True

  def addULData(self, data):
    self.data += self.__d2s(data, 4)
    return True

  def clear(self):
    self.data = ""
    self.dlen = ""
    self.msg = ""

  def __d2s(self, d, size = 4):
    d = hex(d)[2 : ]
    if d[-1] == "L":
      d = d[ : -1]
    ln = len(d)
    if ln % 2:
      ln += 1
      d = "0" + d
    i = 0
    r = ""
    while i &lt; ln:
      r = chr(int(d[i : i + 2], 16)) + r
      i += 2
    while len(r) &lt; size:
      r += chr(0x00)
    return r

  def __s2d(self, s):
    r = ""
    i = len(s)
    while i:
      t = hex(ord(s[i - 1: i]))[2 : ]
      if len(t) &lt; 2: t = "0" + t
      r += t
      i -= 1
    return int(r, 16)
</pre>
<p>Теперь создадим файл emails.py в котором у нас будут храниться функции, связанные с обработкой аккаунтов. Для хранения информации об аккаунтах я выбрал sqlite. На это меня побудило 2 причины: 1 &#8212; это удобно :), 2 &#8212; мне надо было разобраться особенностями работы с СУБД из-под python (в частности, надо было разобраться с sqlalchemy).</p>
<p>В emails.py у нас содержится описание таблицы с нашими аккаунтами, в которой будут следующие поля:</p>
<ul>
<li>login &#8212; собственно, сам email;</li>
<li>password &#8212; пароль от него;</li>
<li>limit &#8212; по умолчанию равен 50 &#8212; количество sms, которое можно отправить в сутки с аккаунта (mail.ru не позволяет больше);</li>
<li>last_sms &#8212; время отправки последней sms, mail.ru запрещает слать sms чаще, чем раз в минуту;</li>
</ul>
<p>А так же следующие функции:</p>
<ul>
<li>add_emails(emails, session) &#8212; открывает файл с мыльниками и паролями (разделены пробелами) и добавляет их в нашу базу, если таких аккаунтов там ещё нет, возвращает число добавленных аккаунтов;</li>
<li>update_limits(session) &#8212; устанавливает лимит sms равный 50 у тех аккаунтов, у которых со времени последней отправки sms прошло больше суток;</li>
</ul>
<p>Код у меня выглядит так:</p>
<pre class="brush: python">
import sqlalchemy as sa
from sqlalchemy.orm import mapper, sessionmaker
import time

engine = sa.create_engine("sqlite:///database.db", echo=False)
metadata = sa.MetaData()

emails_table = sa.Table("emails", metadata,
                        sa.Column("email", sa.String(convert_unicode=False), primary_key=True),
                        sa.Column("password", sa.String(convert_unicode=False), nullable=False),
                        sa.Column("limit", sa.Integer, nullable=False, default=50),
                        sa.Column("last_sms", sa.Integer, nullable=False, default=0)
                       )

metadata.create_all(engine)

class Email(object):
  def __init__(self, email, password, limit = 50, last_sms = 0):
    self.email      = email
    self.password   = password
    self.limit      = limit
    self.last_sms   = last_sms

  def __repr__(self):
    return "" % (self.email, self.password, self.limit, self.last_sms)

def add_emails(emails, session):
  try:
    femails = open(emails, "r")
  except:
    print "Error. Can't open file %s." % emails
    return False
  try:
    counter = 0
    for e in femails:
      login, password = e.strip("\r\n").split(" ", 1)
      c = session.query(Email).filter(Email.email == login).count()
      if not c:
        session.add(Email(login, password))
        counter += 1
    session.commit()
  except:
    femails.close()
    print "Error. Unknow exception :-(."
    return False
  femails.close()
  return counter

def update_limits(session):
  e = session.query(Email).filter(Email.last_sms &lt;= int(time.mktime(time.localtime()) - 86400))
  for email in e:
    email.limit = 50
  session.commit()

mapper(Email, emails_table)
Session = sessionmaker(bind = engine)
</pre>
<p>Теперь пришло время заняться самим клиентом. Создаём очередной файл clnt.py и описываем в нём класс со следующими методами:</p>
<ul>
<li>connect() &#8212; создание сокета и подключение к серверу;</li>
<li>hello() &#8212; отправка MRIM_CS_HELLO и получение в ответ MROM_CS_HELLO_ACK;</li>
<li>auth(login, passwd) &#8212; отправка запроса на авторизацию;</li>
<li>sendSMS(number, text) &#8212; отправить sms на номер number. Стоит отметить, что тип пакета с sms &#8212; 0&#215;00001039, сам пакет имеет следующий формат &#8212; UL 0&#215;00000000 LPS +71234567890, LPS текст sms, где +71234567890 &#8212; номер на который мы отправляем сообщение. Так же ещё одна особенность &#8212; если среди наших контактов в агенте нет ни одного с указанным номером телефона, то сообщения не будут доходить;</li>
<li>send(msg) &#8212; пишет msg в уже открытый сокет;</li>
<li>get() &#8212; читает из сокета и возвращает результат;</li>
<li>close() &#8212; закрывает сокет</li>
<li>start(login, passwd) &#8212; вызывает последовательно все необходимые методы для подключения к серверу;</li>
<li>__init__(rhost, rport) &#8212; конструктор, ему передаётся ip-адрес и порт к которому будет производиться подключение;</li>
</ul>
<pre class="brush: python"># -*- coding: cp1251 -*-

import socket
import os
import urllib
from packets import *

class clnt:
  rhost = ""
  rport = 0
  sock = False
  ping = 0
  packet = False
  rcvd_packet = False

  def __init__(self, rhost, rport):
    self.rhost, self.rport = rhost, rport
    self.packet = p_header()
    self.rcvd_packet = p_header()

  def connect(self):
    # Создаём сокет и подключаемся
    self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
      self.sock.connect((self.rhost, self.rport))
      addr, port = self.sock.getsockname()
      self.packet.setHost(addr, port)
    except:
      print "\nError. Connection to server %s:%d failed." % (self.rhost, self.rport)
      return False
    return True

  def start(self, login, passwd):
    if not self.connect(): return False
    if not self.hello(): return False
    if not self.auth(login, passwd): return False

    return True

  def hello(self):
    self.send(self.packet.getPacket(MRIM_CS_HELLO))
    #print "Sent MRIM_CS_HELLO."
    try:
      self.rcvd_packet.setPacket(self.get())
      if self.rcvd_packet.msg == MRIM_CS_HELLO_ACK:
        #print "Recieved MRIM_CS_HELLO_ACK.",
        self.ping = self.rcvd_packet.data
        #print "Ping timeout %ds." % self.ping
      else:
        print "\nError. Not recieved MRIM_CS_HELLO_ACK."
        return False
    except:
      print "\nError. Not recieved MRIM_CS_HELLO_ACK."
      return False
    return True

  def auth(self, login, passwd):
    self.packet.clear()
    self.packet.addLPSData(login)
    self.packet.addLPSData(passwd)
    self.packet.addULData(STATUS_ONLINE)
    self.packet.addLPSData("STATUS_ONLINE")
    self.packet.addLPSData("FreeAgent v 0.1")
    print "Trying to authorize as %s..." % login,
    try:
      self.send(self.packet.getPacket(MRIM_CS_LOGIN2))
    except:
      print "\nError. Can't send MRIM_CS_LOGIN2."
      return False
    try:
      self.rcvd_packet.setPacket(self.get())
    except:
      print "\nError. Not recievd answer from server."
      return False
    if self.rcvd_packet.msg == MRIM_CS_LOGIN_ACK:
      print "success!"
      return True
    else:
      print "error :-(."
      return False

  def sendSMS(self, number, text):
    ftext = ""
    ftext = text
    self.packet.clear()
    self.packet.addULData(0x0)
    self.packet.addLPSData(number)
    self.packet.addLPSData(ftext)
    print "Sending to %s: %s..." % (number, text),
    try:
      self.send(self.packet.getPacket(0x00001039))
    except:
      print "\nError. Can't send sms"
      return False
    print "Ok!"
    return True

  def send(self, msg):
    try:
      self.sock.send(msg)
    except:
      print "\nError. Can't send data."
      return False
    return True

  def get(self):
    return self.sock.recv(4096)

  def close(self):
    self.sock.close()
    return True
</pre>
<p>Все основные функции и классы написаны, осталось их только использовать. Создаём главный файл sms.py.<br />
Первое, что мы в нём делаем &#8212; это &#171;обновляем&#187; лимиты в 50 сообщений в сутки, далее проверяем количество аргументов командной строки, если оно равно трём, то считаем, что нам переданы номер, на который надо отправить sms, и текст (т.е. программа была запущена так: sms.py +71234567890 &#171;testovoe soobshenie&#187;), иначе входим в интерактивный режим.<br />
Следующий шаг &#8212; пытаемся подключиться к адресу &#171;http://mrim.mail.ru:2042&#8243; и узнать адрес сервера к которому нам рекомендуют подключаться на данный момент.<br />
Выбираем из нашей БД список ящиков, у которых не превышен лимит, упорядочиваем (по возрастанию) по дате отправки последнего сообщения и отправляем sms с 1го, уменьшая лимит этого ящика на 1. Если со времени последней отправки sms не прошла ещё 61 секунда, то перед отправкой ждём.</p>
<pre class="brush: python"># -*- coding: cp1251 -*-

from clnt import *
import sys
import time
import urllib
from emails import *
import os

session = Session()
update_limits(session)

if len(sys.argv) == 3:
  number = sys.argv[1]
  text = sys.argv[2]
else:
  while True:
    action = raw_input("Select action:\n1 - add emails from file\n2 - send sms\n")
    if action == "1":
      ef = raw_input("Enter file name: ")
      print "%d new emails added." % add_emails(ef, session)
    elif action == "2":
      number = raw_input("Enter phone number: ")
      text = raw_input("Enter SMS text: ")
      break
    else:
      print "Invalid action!"

try:
  saddr = urllib.urlopen("http://mrim.mail.ru:2042").readlines()
  saddr = saddr[0]
  shost, sport = saddr[:-1].split(":")
  sport = int(sport)
except:
  print "\nError. Can't connect to mrim.mail.ru."
  sys.exit(0)

emails = session.query(Email).filter(Email.limit &gt; 0).order_by(Email.last_sms)
if not emails.count():
  print "Sorry, all limits exceeded."
  sys.exit(0)
if emails[0].last_sms &gt; (int(time.mktime(time.localtime()) - 61)):
  print "Waiting %d seconds." % (61 - (int(time.mktime(time.localtime())) - emails[0].last_sms))
  time.sleep(61 - (int(time.mktime(time.localtime())) - emails[0].last_sms))
c = clnt(shost, sport)
if not c.start(str(emails[0].email), str(emails[0].password)):
  print "Error."
  sys.exit(0)
c.sendSMS(number, text)
emails[0].last_sms = int(time.mktime(time.localtime()))
emails[0].limit -= 1
session.commit()
c.close()
session.commit()
</pre>
<p>На этом наша программа таки уже закончена. Стоит отметить что не проверяется длина сообщений и отправка кириллицы протестирована только в кодировке CP1251. Все использованные файлы можно скачать из <a href="http://zetblog.ru/wp-files/15/sms.rar">прикреплённого архива</a>.</p>
<p>Ссылки к статье:<br />
<a href="http://agent.mail.ru/ru/developers/protocol.html">http://agent.mail.ru/ru/developers/protocol.html</a> &#8212; описание протокола агента.<br />
<a href="http://sqlalchemy.org">http://sqlalchemy.org</a> &#8212; официальный сайт sqlalchemy.<br />
<a href="http://www.arechisoft.com/">http://www.arechisoft.com/</a> &#8212; пакетный снифер EtherSnoop.<br />
<a href="http://yo.jabber.ru/files/mrim/">http://yo.jabber.ru/files/mrim/</a> &#8212; Mrim &#8212; это Jabber-транспорт для IM-сервиса компании Mail.Ru написанный на python.</p>
]]></content:encoded>
			<wfw:commentRss>http://zetblog.ru/programming/200811/python-%d0%b1%d0%b5%d1%81%d0%bf%d0%bb%d0%b0%d1%82%d0%bd%d0%b0%d1%8f-%d0%be%d1%82%d0%bf%d1%80%d0%b0%d0%b2%d0%ba%d0%b0-sms-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-mailru/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
	</channel>
</rss>

