<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4934018258162745792</id><updated>2010-09-04T07:56:25.781-07:00</updated><title type='text'>Johannes Plunien</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.pqpq.de/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default?orderby=updated'/><link rel='alternate' type='text/html' href='http://www.pqpq.de/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/07403140829864430702</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>9</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4934018258162745792.post-2919363204507339629</id><published>2010-06-09T23:12:00.000-07:00</published><updated>2010-06-09T23:15:46.435-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='GPW GPW2010 Gearman::Driver Perl CPAN Schorndorf'/><title type='text'>Gearman::Driver @ XING (GPW2010)</title><content type='html'>Yesterday I gave a talk at the &lt;a href="http://conferences.yapceurope.org/gpw2010/"&gt;German Perl Workshop&lt;/a&gt; in Schorndorf about &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; @ &lt;a href="http://www.xing.com"&gt;XING&lt;/a&gt;. The &lt;a href="http://dl.dropbox.com/u/1365846/talks/gpw2010/index.html"&gt;slides&lt;/a&gt; can be found &lt;a href="http://dl.dropbox.com/u/1365846/talks/gpw2010/index.html"&gt;here&lt;/a&gt; as well as the &lt;a href="http://dl.dropbox.com/u/1365846/talks/gpw2010/code.tar.gz"&gt;code examples&lt;/a&gt; I've used in the &lt;a href="http://dl.dropbox.com/u/1365846/talks/gpw2010/index.html"&gt;slides&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-2919363204507339629?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.pqpq.de/feeds/2919363204507339629/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.pqpq.de/2010/06/gearmandriver-xing-gpw2010.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/2919363204507339629'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/2919363204507339629'/><link rel='alternate' type='text/html' href='http://www.pqpq.de/2010/06/gearmandriver-xing-gpw2010.html' title='Gearman::Driver @ XING (GPW2010)'/><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/07403140829864430702</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='10374274144747074890'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4934018258162745792.post-965929689729547857</id><published>2008-07-16T23:38:00.000-07:00</published><updated>2010-04-07T05:30:01.412-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MojoMojo'/><category scheme='http://www.blogger.com/atom/ns#' term='Perl'/><title type='text'>MojoMojo - make it private</title><content type='html'>&lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt; is a wiki written in &lt;a href="http://catalyst.perl.org/"&gt;Catalyst&lt;/a&gt; and &lt;a href="http://search.cpan.org/dist/DBIx-Class"&gt;DBIx::Class&lt;/a&gt;. It was the best choice to replace my private &lt;a href="http://www.mediawiki.org/wiki/MediaWiki/de"&gt;MediaWiki&lt;/a&gt;. The installation is straight forward:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;svn co http://code2.0beta.co.uk/mojomojo/svn/trunk/ MojoMojo&lt;br /&gt;cd MojoMojo&lt;br /&gt;perl Makefile.PL&lt;br /&gt;make&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The standard way to install MojoMojo is through CPAN. You can find the distribution for it &lt;a href="http://search.cpan.org/dist/MojoMojo/"&gt;here&lt;/a&gt;, or you can install it from the command line like this:&lt;br /&gt;&lt;pre class="brush: perl"&gt;sudo cpan MojoMojo&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;After successful installation &lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt; needs to be configured using its mojomojo.conf. Generally you can just edit this file directly, but if you're using the svn checkout and probably want to contribute some changes later you better go for a new mojomojo_local.conf. So you can have your database credentials in that file and you don't have to worry about committing it by accident.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&amp;lt;Model::DBIC&amp;gt;&lt;br /&gt;    connect_info   dbi:mysql:mojomojo&lt;br /&gt;    connect_info   mojomojo&lt;br /&gt;    connect_info   sTrOnGpAsSwOrD&lt;br /&gt;&amp;lt;/Model::DBIC&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Create database tables:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;./script/mojomojo_spawn_db.pl&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Done. Now you can deploy &lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt; using &lt;a href="http://perl.apache.org/"&gt;mod_perl&lt;/a&gt;, &lt;a href="http://www.fastcgi.com/"&gt;FastCGI&lt;/a&gt; or just run it using Catalysts development webserver:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;./script/mojomojo_server -p 3000&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;My previous &lt;a href="http://www.mediawiki.org/wiki/MediaWiki/de"&gt;MediaWiki&lt;/a&gt; installation was only accessible by username and password. So i needed to get my head around the authorization implementation in &lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt;. First of all you need to add some lines to the config:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&amp;lt;permissions&amp;gt;&lt;br /&gt;check_permission_on_view    1&lt;br /&gt;cache_permission_data       1&lt;br /&gt;create_allowed              0&lt;br /&gt;delete_allowed              0&lt;br /&gt;edit_allowed                0&lt;br /&gt;view_allowed                0&lt;br /&gt;attachment_allowed          0&lt;br /&gt;&amp;lt;/permissions&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Additionally there are a few database changes necessary:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;-- add a new role&lt;br /&gt;insert into role values (null, 'user', 1);&lt;br /&gt;&lt;br /&gt;-- add this role to the standard admin user&lt;br /&gt;insert into role_member values (1, 2, 1);&lt;br /&gt;&lt;br /&gt;-- allow everything to that new role&lt;br /&gt;insert into path_permissions values&lt;br /&gt;('/', 1, 'no', 'yes', 'yes', 'yes', 'yes', 'yes');&lt;br /&gt;insert into path_permissions values&lt;br /&gt;('/', 1, 'yes', 'yes', 'yes', 'yes', 'yes', 'yes');&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;How does it look like now?&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;mysql&amp;gt; select * from role;&lt;br /&gt;+----+------+--------+&lt;br /&gt;| id | name | active |&lt;br /&gt;+----+------+--------+&lt;br /&gt;|  1 | user |      1 |&lt;br /&gt;+----+------+--------+&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; select * from role_member;&lt;br /&gt;+------+--------+-------+&lt;br /&gt;| role | person | admin |&lt;br /&gt;+------+--------+-------+&lt;br /&gt;|    1 |      2 |     1 |&lt;br /&gt;+------+--------+-------+&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; select * from path_permissions\G&lt;br /&gt;*************************** 1. row ***************************&lt;br /&gt;path: /&lt;br /&gt;role: 1&lt;br /&gt;apply_to_subpages: no&lt;br /&gt;create_allowed: yes&lt;br /&gt;delete_allowed: yes&lt;br /&gt;edit_allowed: yes&lt;br /&gt;view_allowed: yes&lt;br /&gt;attachment_allowed: yes&lt;br /&gt;*************************** 2. row ***************************&lt;br /&gt;path: /&lt;br /&gt;role: 1&lt;br /&gt;apply_to_subpages: yes&lt;br /&gt;create_allowed: yes&lt;br /&gt;delete_allowed: yes&lt;br /&gt;edit_allowed: yes&lt;br /&gt;view_allowed: yes&lt;br /&gt;attachment_allowed: yes&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To get this working please make sure to use at least version 0.999018 or revision 927 of &lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In next release of &lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt; there will be also a new flag to enforce login:&lt;br /&gt;&lt;pre class="brush: perl"&gt;&amp;lt;permissions&amp;gt;&lt;br /&gt;    enforce_login 1&lt;br /&gt;&amp;lt;/permissions&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-965929689729547857?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.pqpq.de/feeds/965929689729547857/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.pqpq.de/2008/07/mojomojo-make-it-private.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/965929689729547857'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/965929689729547857'/><link rel='alternate' type='text/html' href='http://www.pqpq.de/2008/07/mojomojo-make-it-private.html' title='MojoMojo - make it private'/><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/07403140829864430702</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='10374274144747074890'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4934018258162745792.post-2523139879215863833</id><published>2008-07-26T04:17:00.000-07:00</published><updated>2010-04-07T05:28:51.930-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='Perl'/><category scheme='http://www.blogger.com/atom/ns#' term='ExtJS'/><title type='text'>Remote form validation</title><content type='html'>&lt;a href="http://catalyst.perl.org/"&gt;Catalyst&lt;/a&gt; and &lt;a href="http://extjs.com/"&gt;ExtJS&lt;/a&gt; are my favourite frameworks for developing web applications. &lt;a href="http://extjs.com/"&gt;ExtJS&lt;/a&gt; provides a lot features to validate form data before the form is being submitted to the server. Everybody knows that you never should trust those values even if it was validated in the browser by &lt;a href="http://extjs.com/"&gt;ExtJS&lt;/a&gt;. Here is a comfortable way to validate form values remotely using &lt;a href="http://search.cpan.org/dist/Catalyst-Plugin-FormValidator-Simple/"&gt;Catalyst::Plugin::FormValidator::Simple&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The full example can be found on &lt;a href="http://github.com/plu/rfv-example/tree/master"&gt;github&lt;/a&gt;:&lt;br /&gt;&lt;strong&gt;git clone git://github.com/plu/rfv-example.git&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Create an &lt;a href="http://extjs.com/"&gt;ExtJS&lt;/a&gt; FormPanel:&lt;br /&gt;&lt;pre class="brush: jscript"&gt;&lt;br /&gt;var win = new Ext.Window({&lt;br /&gt;    .....&lt;br /&gt;    ,items:[&lt;br /&gt;        new Ext.FormPanel({&lt;br /&gt;            ,url:'[% Catalyst.uri_for('/save') %]'&lt;br /&gt;            ,id: 'fp'&lt;br /&gt;            .....&lt;br /&gt;            ,items: [{&lt;br /&gt;                fieldLabel: 'First name',&lt;br /&gt;                name: 'first',&lt;br /&gt;                anchor:'100%'&lt;br /&gt;            },{&lt;br /&gt;                fieldLabel: 'Last name',&lt;br /&gt;                name: 'last',&lt;br /&gt;                anchor:'100%'&lt;br /&gt;            },{&lt;br /&gt;                fieldLabel: 'Company',&lt;br /&gt;                name: 'company',&lt;br /&gt;                anchor:'100%'&lt;br /&gt;            },{&lt;br /&gt;                fieldLabel: 'eMail',&lt;br /&gt;                name: 'email',&lt;br /&gt;                anchor:'100%'&lt;br /&gt;            }]&lt;br /&gt;            ,buttons: [{&lt;br /&gt;                text: 'Save',&lt;br /&gt;                handler: function() {&lt;br /&gt;                    win.findById('fp').getForm().submit();&lt;br /&gt;                }&lt;br /&gt;            }]&lt;br /&gt;        })&lt;br /&gt;    ]&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Read validation results and modify form fields:&lt;br /&gt;&lt;pre class="brush: jscript"&gt;&lt;br /&gt;var fp = win.findById('fp');&lt;br /&gt;fp.on('actioncomplete', function(a, fp) {&lt;br /&gt;    var r = fp.result;&lt;br /&gt;    for (var field in r.error) {&lt;br /&gt;        fp.form.findField(field).markInvalid(&lt;br /&gt;            r.error[field].join("&amp;lt;br/&amp;gt;")&lt;br /&gt;        );&lt;br /&gt;    }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Validate form values:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;sub save : Global {&lt;br /&gt;    my ( $self, $c ) = @_;&lt;br /&gt;&lt;br /&gt;    $c-&gt;form(&lt;br /&gt;        first   =&gt; [ qw/NOT_BLANK ASCII/ =&gt; [qw/LENGTH 2 20/] ],&lt;br /&gt;        last    =&gt; [ qw/NOT_BLANK ASCII/ =&gt; [qw/LENGTH 2 20/] ],&lt;br /&gt;        company =&gt; [ qw/NOT_BLANK ASCII/ =&gt; [qw/LENGTH 2 20/] ],&lt;br /&gt;        email   =&gt; [qw/NOT_BLANK EMAIL_LOOSE/],&lt;br /&gt;    );&lt;br /&gt;&lt;br /&gt;    my $json : Stashed = { error =&gt; $c-&gt;form-&gt;field_messages('save'), };&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-2523139879215863833?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.pqpq.de/feeds/2523139879215863833/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.pqpq.de/2008/07/remote-form-validation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/2523139879215863833'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/2523139879215863833'/><link rel='alternate' type='text/html' href='http://www.pqpq.de/2008/07/remote-form-validation.html' title='Remote form validation'/><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/07403140829864430702</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='10374274144747074890'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4934018258162745792.post-3129471095401351225</id><published>2008-07-29T00:12:00.000-07:00</published><updated>2010-04-07T05:24:58.764-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='Firebug'/><title type='text'>Firebug debugging fallback</title><content type='html'>&lt;a href="https://addons.mozilla.org/de/firefox/addon/1843"&gt;Firebug&lt;/a&gt; is the best tool for debugging heavyweight javascript web applications. You can put &lt;strong&gt;console.log(arguments)&lt;/strong&gt; here and &lt;strong&gt;console.debug(foo)&lt;/strong&gt; there, but ... YOU SHOULD NEVER FORGET TO REMOVE IT! If you forget it, your application stops working in any browser that doesn't know those methods. To avoid this, just include this snippet:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: jscript"&gt;&lt;br /&gt;if(!window.console || !window.console.firebug) {&lt;br /&gt;  window.console = {&lt;br /&gt;    debug: function(){},&lt;br /&gt;    log: function(){}&lt;br /&gt;  };&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;/Edit: I just found &lt;a href="http://getfirebug.com/firebug/firebugx.js"&gt;http://getfirebug.com/firebug/firebugx.js&lt;/a&gt; which is more complete:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: jscript"&gt;&lt;br /&gt;if (!window.console || !console.firebug)&lt;br /&gt;{&lt;br /&gt;    var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",&lt;br /&gt;    "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];&lt;br /&gt;&lt;br /&gt;    window.console = {};&lt;br /&gt;    for (var i = 0; i &lt; names.length; ++i)&lt;br /&gt;        window.console[names[i]] = function() {}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-3129471095401351225?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.pqpq.de/feeds/3129471095401351225/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.pqpq.de/2008/07/firebug-debugging-fallback.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/3129471095401351225'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/3129471095401351225'/><link rel='alternate' type='text/html' href='http://www.pqpq.de/2008/07/firebug-debugging-fallback.html' title='Firebug debugging fallback'/><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/07403140829864430702</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='10374274144747074890'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4934018258162745792.post-2468241276196862242</id><published>2008-10-02T01:01:00.000-07:00</published><updated>2010-04-07T05:22:55.723-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='irssi perl poe'/><category scheme='http://www.blogger.com/atom/ns#' term='&quot;irssi perl poe&quot;'/><title type='text'>irssi: Close all queries</title><content type='html'>This irssi script creates a new command to close all open queries at once. I don't know what else to explain here, i think the script explains itself.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use Irssi;&lt;br /&gt;use Glib;&lt;br /&gt;use POE qw(Loop::Glib Session::Irssi);&lt;br /&gt;&lt;br /&gt;my $VERSION = '0.1';&lt;br /&gt;my %IRSSI   = (&lt;br /&gt;    authors =&gt; 'Johannes Plunien',&lt;br /&gt;    contact =&gt; 'http://www.pqpq.de/contact/',&lt;br /&gt;    license =&gt; 'Perl',&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;POE::Session::Irssi-&gt;create(&lt;br /&gt;    irssi_commands =&gt; {&lt;br /&gt;        qc =&gt; sub {&lt;br /&gt;            foreach my $w ( Irssi::windows() ) {&lt;br /&gt;                my $act = $w-&gt;{active};&lt;br /&gt;                next unless defined $act-&gt;{type};&lt;br /&gt;                $w-&gt;command("window close") if $act-&gt;{type} eq 'QUERY';&lt;br /&gt;            }&lt;br /&gt;        },&lt;br /&gt;    },&lt;br /&gt;);&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-2468241276196862242?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.pqpq.de/feeds/2468241276196862242/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.pqpq.de/2008/10/irssi-close-all-queries.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/2468241276196862242'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/2468241276196862242'/><link rel='alternate' type='text/html' href='http://www.pqpq.de/2008/10/irssi-close-all-queries.html' title='irssi: Close all queries'/><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/07403140829864430702</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='10374274144747074890'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4934018258162745792.post-5249548867991234258</id><published>2009-02-01T09:44:00.000-08:00</published><updated>2010-04-07T05:21:02.345-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='&quot;irssi perl poe twitter&quot;'/><category scheme='http://www.blogger.com/atom/ns#' term='irssi perl poe twitter'/><title type='text'>irssi: Twitter</title><content type='html'>&lt;ul&gt;&lt;br /&gt;&lt;li&gt;install all dependencies (POE, POE::Loop::Glib, POE::Session::Irssi, Net::Twitter, HTML::Entities, HTTP::Date)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;change credentials&lt;/li&gt;&lt;br /&gt;&lt;li&gt;/script load /path/to/twitter.pl&lt;/li&gt;&lt;br /&gt;&lt;li&gt;/window new hidden&lt;/li&gt;&lt;br /&gt;&lt;li&gt;change to the new window and type /window name twitter&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use Irssi;&lt;br /&gt;use Glib;&lt;br /&gt;use POE qw(Loop::Glib Session::Irssi);&lt;br /&gt;use Net::Twitter;&lt;br /&gt;use HTML::Entities qw( decode_entities );&lt;br /&gt;use HTTP::Date qw( time2str );&lt;br /&gt;&lt;br /&gt;my $VERSION = '0.1';&lt;br /&gt;my %IRSSI   = (&lt;br /&gt;    authors =&gt; 'Johannes Plunien',&lt;br /&gt;    contact =&gt; 'http://www.pqpq.de/contact/',&lt;br /&gt;    name    =&gt; 'Twitter',&lt;br /&gt;    license =&gt; 'Perl',&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;my %twitter_config = (&lt;br /&gt;    username        =&gt; 'plutooth',&lt;br /&gt;    password        =&gt; 'secret',&lt;br /&gt;    update_interval =&gt; 90,           # seconds&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;POE::Session::Irssi-&gt;create(&lt;br /&gt;    irssi_commands =&gt; {&lt;br /&gt;        tmsg =&gt; sub {&lt;br /&gt;            $_[HEAP]-&gt;{twitter}-&gt;update( join " ", ( @{ $_[ARG1] } )[0] );&lt;br /&gt;            $_[KERNEL]-&gt;delay( update =&gt; 5 );&lt;br /&gt;        },&lt;br /&gt;    },&lt;br /&gt;    inline_states =&gt; {&lt;br /&gt;        _start =&gt; sub {&lt;br /&gt;            my $heap = $_[HEAP];&lt;br /&gt;            $heap-&gt;{window} = Irssi::window_find_name('twitter');&lt;br /&gt;            Irssi::print("Create a window named 'twitter'") if !$heap-&gt;{window};&lt;br /&gt;            my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];&lt;br /&gt;            $heap-&gt;{interval} = delete $twitter_config{update_interval};&lt;br /&gt;            $heap-&gt;{twitter}  = Net::Twitter-&gt;new(%twitter_config)&lt;br /&gt;              or Irssi::print("Twitter ERROR: $!");&lt;br /&gt;            $kernel-&gt;delay( update =&gt; 2 );&lt;br /&gt;        },&lt;br /&gt;        update =&gt; sub {&lt;br /&gt;            my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];&lt;br /&gt;            my $params = {};&lt;br /&gt;            $params-&gt;{since} = $heap-&gt;{last_update} if defined $heap-&gt;{last_update};&lt;br /&gt;            my $timeline    = $heap-&gt;{twitter}-&gt;friends_timeline($params);&lt;br /&gt;            my $http_status = $heap-&gt;{twitter}-&gt;http_code;&lt;br /&gt;            $heap-&gt;{window} = Irssi::window_find_name('twitter');&lt;br /&gt;            if (   $http_status == 200&lt;br /&gt;                &amp;&amp; $heap-&gt;{window} )&lt;br /&gt;            {&lt;br /&gt;                $heap-&gt;{last_update} = time2str(time);&lt;br /&gt;                $kernel-&gt;yield( display =&gt; $timeline ) if scalar @$timeline &gt; 0;&lt;br /&gt;            }&lt;br /&gt;            $kernel-&gt;delay( update =&gt; $heap-&gt;{interval} );&lt;br /&gt;        },&lt;br /&gt;        display =&gt; sub {&lt;br /&gt;            my ( $kernel, $heap, $timeline ) = @_[ KERNEL, HEAP, ARG0 ];&lt;br /&gt;            foreach my $row ( reverse @$timeline ) {&lt;br /&gt;                my $screen_name = $row-&gt;{user}{screen_name};&lt;br /&gt;                my $text        = $row-&gt;{text};&lt;br /&gt;                $text =~ s/\n/\n   /;&lt;br /&gt;                my $cleantext = decode_entities($text);&lt;br /&gt;                my $message   = "&lt;$screen_name&gt; $cleantext";&lt;br /&gt;                $heap-&gt;{window}-&gt;print( $message, MSGLEVEL_PUBLIC );&lt;br /&gt;            }&lt;br /&gt;        },&lt;br /&gt;    },&lt;br /&gt;);&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-5249548867991234258?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.pqpq.de/feeds/5249548867991234258/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.pqpq.de/2009/02/irssi-twitter.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/5249548867991234258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/5249548867991234258'/><link rel='alternate' type='text/html' href='http://www.pqpq.de/2009/02/irssi-twitter.html' title='irssi: Twitter'/><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/07403140829864430702</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='10374274144747074890'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4934018258162745792.post-7515179558737801368</id><published>2009-05-27T13:39:00.000-07:00</published><updated>2010-04-07T05:19:53.119-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='&quot;perl catalyst dbic dao ironman dbix::class&quot;'/><category scheme='http://www.blogger.com/atom/ns#' term='perl catalyst dbic dao ironman dbix::class'/><title type='text'>Catalyst + DBIC + DAO</title><content type='html'>Recently there was a &lt;a href="http://www.mail-archive.com/catalyst@lists.scsys.co.uk/msg06932.html"&gt;question&lt;/a&gt; on the &lt;a href="http://www.catalystframework.org/"&gt;Catalyst&lt;/a&gt; &lt;a href="http://dev.catalystframework.org/wiki/mailinglists"&gt;mailing list&lt;/a&gt; which i've been asked by some teammate too: "How do i create DataAccessObjects with methods i can use in Catalyst as well as in CronJobs or other scripts?"&lt;br /&gt;&lt;br /&gt;You're too lazy to read the full entry, you just want to see the code? It's available on &lt;a href="http://www.github.com/"&gt;GitHub&lt;/a&gt;: &lt;a href="http://github.com/plu/dao-example/tree/master"&gt;http://github.com/plu/dao-example/tree/master&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It's quite easy, all you need is custom &lt;a href="http://search.cpan.org/dist/DBIX-Class"&gt;DBIx::Class&lt;/a&gt; ResultSets. I guess you've already setup a &lt;a href="http://search.cpan.org/dist/Catalyst-Model-DBIC-Schema"&gt;model&lt;/a&gt; in your Catalyst app:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;package DAO::Example::Model::DB;&lt;br /&gt;&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use base 'Catalyst::Model::DBIC::Schema';&lt;br /&gt;&lt;br /&gt;1;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;...and a config file that might look like:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;# rename this file to DAO::Example.yml and put a : in front of "name" if&lt;br /&gt;# you want to use yaml like in old versions of Catalyst&lt;br /&gt;name DAO::Example&lt;br /&gt;&lt;br /&gt;&amp;lt;model::db&amp;gt;&lt;br /&gt;    schema_class    DAO::Example::DB&lt;br /&gt;    connect_info    dbi:SQLite:dao_example.db&lt;br /&gt;    connect_info    username&lt;br /&gt;    connect_info    password&lt;br /&gt;&amp;lt;/model::db&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To get custom resultsets for all of your result classes setup the schema using load_namespaces:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;package DAO::Example::DB;&lt;br /&gt;&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use base qw/DBIx::Class::Schema/;&lt;br /&gt;&lt;br /&gt;__PACKAGE__-&gt;load_namespaces( default_resultset_class =&gt; '+DAO::Example::DB::Base::ResultSet' );&lt;br /&gt;&lt;br /&gt;1;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Going that way your result classes will be expected in DAO::Example::DB::Result:: namespace, resultsets in DAO::Example::DB::ResultSet::. If you create a class DAO::Example::DB::Result::Person DBIC will look if DAO::Example::DB::ResultSet::Person exists and inherit all person resultsets from that class. If there's no such class the resultset will be inherited from DAO::Example::DB::Base::ResultSet.&lt;br /&gt;&lt;br /&gt;Let's add some methods to the person resultset:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;package DAO::Example::DB::ResultSet::Person;&lt;br /&gt;&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use base qw/DAO::Example::DB::Base::ResultSet/;&lt;br /&gt;&lt;br /&gt;sub by_username {&lt;br /&gt;    my ( $rs, $username ) = @_;&lt;br /&gt;    return $rs-&gt;search( { 'me.username' =&gt; $username }, { key =&gt; 'unique_username' } );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub prefetch_all {&lt;br /&gt;    my ($rs) = @_;&lt;br /&gt;    return $rs-&gt;search( {}, { prefetch =&gt; [ { personroles =&gt; [qw/role/] } ] } );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;1;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And to the default resultset:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;package DAO::Example::DB::Base::ResultSet;&lt;br /&gt;&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use base qw/DBIx::Class::ResultSet::HashRef DAO::Example::DB::Base::Any/;&lt;br /&gt;&lt;br /&gt;sub active {&lt;br /&gt;    my ($rs) = @_;&lt;br /&gt;    return $rs-&gt;search( { 'me.active' =&gt; 1 } );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub inactive {&lt;br /&gt;    my ($rs) = @_;&lt;br /&gt;    return $rs-&gt;search( { 'me.active' =&gt; { '!=' =&gt; 1 } } );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;1;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In catalyst you would call these methods that way:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;package DAO::Example::Controller::Root;&lt;br /&gt;&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use parent 'Catalyst::Controller';&lt;br /&gt;use Data::Dumper;&lt;br /&gt;$Data::Dumper::Sortkeys = 1;&lt;br /&gt;&lt;br /&gt;__PACKAGE__-&gt;config-&gt;{namespace} = '';&lt;br /&gt;&lt;br /&gt;sub index : Path : Args(0) {&lt;br /&gt;    my ( $self, $c ) = @_;&lt;br /&gt;&lt;br /&gt;    unless ( -e $c-&gt;path_to('dao_example.db') ) {&lt;br /&gt;        $c-&gt;model('DB')-&gt;schema-&gt;deploy;&lt;br /&gt;        $c-&gt;model('DB')-&gt;schema-&gt;init;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    $c-&gt;res-&gt;print('&lt;pre&gt;');&lt;br /&gt;    $c-&gt;res-&gt;print( Dumper $c-&gt;config );&lt;br /&gt;    $c-&gt;res-&gt;print( Dumper $c-&gt;model('DB')-&gt;resultset('Person')-&gt;active-&gt;hashref_array );&lt;br /&gt;    $c-&gt;res-&gt;print( Dumper $c-&gt;model('DB')-&gt;resultset('Person')-&gt;inactive-&gt;hashref_array );&lt;br /&gt;    $c-&gt;res-&gt;print( Dumper $c-&gt;model('DB')-&gt;resultset('Person')-&gt;by_username('plu')-&gt;prefetch_all-&gt;hashref_array );&lt;br /&gt;    $c-&gt;res-&gt;print('&lt;/pre&gt;');&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The method &lt;strong&gt;hashref_array&lt;/strong&gt; comes from &lt;a href="http://search.cpan.org/dist/DBIx-Class-ResultSet-HashRef"&gt;DBIx::Class::ResultSet::HashRef&lt;/a&gt;. How would you call that from a CronJob / without Catalyst?&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;&lt;br /&gt;#!/usr/bin/env perl&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use FindBin;&lt;br /&gt;use lib "$FindBin::Bin/../lib";&lt;br /&gt;use DAO::Example::Utils qw/schema config/;&lt;br /&gt;use Data::Dumper;&lt;br /&gt;$Data::Dumper::Sortkeys = 1;&lt;br /&gt;&lt;br /&gt;unless ( -e "$FindBin::Bin/../dao_example.db" ) {&lt;br /&gt;    schema-&gt;deploy;&lt;br /&gt;    schema-&gt;init;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;print Dumper config;&lt;br /&gt;&lt;br /&gt;print Dumper schema-&gt;resultset('Person')-&gt;active-&gt;hashref_array;&lt;br /&gt;&lt;br /&gt;print Dumper schema-&gt;resultset('Person')-&gt;inactive-&gt;hashref_array;&lt;br /&gt;&lt;br /&gt;print Dumper schema-&gt;resultset('Person')-&gt;by_username('plu')-&gt;prefetch_all-&gt;hashref_array;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The "magic" thing is to instantiate a DBIx::Class::Schema object using your Catalyst config file. I tend to write a small utility class to achieve that:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;package DAO::Example::Utils;&lt;br /&gt;&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use base 'Exporter';&lt;br /&gt;use Config::JFDI;&lt;br /&gt;use DAO::Example::DB;&lt;br /&gt;&lt;br /&gt;use vars qw/@EXPORT_OK $schema $config/;&lt;br /&gt;&lt;br /&gt;@EXPORT_OK = qw/&lt;br /&gt;  schema&lt;br /&gt;  config&lt;br /&gt;  /;&lt;br /&gt;&lt;br /&gt;sub config {&lt;br /&gt;    return $config if defined $config;&lt;br /&gt;    $config = Config::JFDI-&gt;new( name =&gt; "DAO::Example" )-&gt;get;&lt;br /&gt;    return $config;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub schema {&lt;br /&gt;    return $schema if defined $schema;&lt;br /&gt;    $schema = DAO::Example::DB-&gt;connect( @{ config-&gt;{'Model::DB'}{connect_info} || [] } );&lt;br /&gt;    return $schema;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;1;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-7515179558737801368?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.pqpq.de/feeds/7515179558737801368/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.pqpq.de/2009/05/catalyst-dbic-dao.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/7515179558737801368'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/7515179558737801368'/><link rel='alternate' type='text/html' href='http://www.pqpq.de/2009/05/catalyst-dbic-dao.html' title='Catalyst + DBIC + DAO'/><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/07403140829864430702</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='10374274144747074890'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4934018258162745792.post-3169163431309682515</id><published>2009-05-28T12:48:00.000-07:00</published><updated>2010-04-07T05:14:44.750-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='&quot;textmate perl&quot;'/><category scheme='http://www.blogger.com/atom/ns#' term='textmate perl'/><title type='text'>Textmate + Perl + New Package</title><content type='html'>Everytime you create a new file in your project you need to type the package name:&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;lib/My/Project/DB/Result/Person.pm =&gt; My::Project::DB::Result::Person&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;That's really annoying, boring and prone to error. For Textmate users there's a simple solution (notice: the script expects your modules in directory "lib"):&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;#!/usr/bin/env perl&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;&lt;br /&gt;my $package = get_package( path =&gt; $ENV{TM_FILEPATH}, walk_up_to =&gt; 'lib' );&lt;br /&gt;my $content = get_content();&lt;br /&gt;&lt;br /&gt;if ($content) {&lt;br /&gt;    $content =~ s/^package (.*?);/package $package;/;&lt;br /&gt;    print $content;&lt;br /&gt;}&lt;br /&gt;else {&lt;br /&gt;    print "package $package;\n\nuse strict;\nuse warnings;\n\n1;";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub get_content {&lt;br /&gt;    local $/ = undef;&lt;br /&gt;    return &lt;&gt;;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub get_package {&lt;br /&gt;    my (%args) = @_;&lt;br /&gt;    my @chunks = split qr~/~, $args{path};&lt;br /&gt;    while ( @chunks &gt; 0 ) {&lt;br /&gt;        last if $chunks[0] eq $args{walk_up_to};&lt;br /&gt;        shift @chunks;&lt;br /&gt;    }&lt;br /&gt;    shift @chunks;&lt;br /&gt;    my $pkg = join '::', @chunks;&lt;br /&gt;    $pkg =~ s/\.pm//g;&lt;br /&gt;    return $pkg;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Save that script and setup a new command in Textmates bundle editor as shown in the screenshot:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://dl.dropbox.com/u/1365846/blog/images/tm_filename2package.png"&gt;&lt;img src="http://dl.dropbox.com/u/1365846/blog/images/tm_filename2package_small.png" alt="bundle editor" width="500" height="318" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Next time you create a new perl module just hit &lt;strong&gt;Cmd + Ctrl + p&lt;/strong&gt; and select "Filename to Package":&lt;br /&gt;&lt;br /&gt;&lt;img src="http://dl.dropbox.com/u/1365846/blog/images/tm_filename2package_cm.png" alt="context menu" width="213" height="105" /&gt;&lt;br /&gt;&lt;br /&gt;If the file is empty the script will insert following code:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;package My::Project::DB::Result::Person;&lt;br /&gt;&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;&lt;br /&gt;1;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If the file contained code already the script will just update the "package ...;" line with the new value. This is very handy if you move a file around and just want to update the package name.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-3169163431309682515?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.pqpq.de/feeds/3169163431309682515/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.pqpq.de/2009/05/textmate-perl-new-package.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/3169163431309682515'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/3169163431309682515'/><link rel='alternate' type='text/html' href='http://www.pqpq.de/2009/05/textmate-perl-new-package.html' title='Textmate + Perl + New Package'/><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/07403140829864430702</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='10374274144747074890'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4934018258162745792.post-7412633010196908549</id><published>2010-01-30T10:42:00.000-08:00</published><updated>2010-04-07T05:08:12.534-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='&quot;perl gearman&quot;'/><category scheme='http://www.blogger.com/atom/ns#' term='perl gearman'/><title type='text'>Gearman::Driver</title><content type='html'>Gearman was initially developed by &lt;a href="http://www.danga.com/gearman/"&gt;Danga&lt;/a&gt;. It's a very nice framework to distribute (sometimes long running) tasks across multiple servers. For a more detailed description see &lt;a href="http://gearman.org/"&gt;gearman.org&lt;/a&gt; where you will also find a rewrite of it in C.&lt;br /&gt;&lt;br /&gt;First let's take a look at it without &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt;, using a plain &lt;a href="http://search.cpan.org/dist/Gearman-XS/"&gt;Gearman::XS&lt;/a&gt; setup:&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;#!/usr/bin/env perl&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use Gearman::XS::Worker;&lt;br /&gt;use Gearman::XS qw(:constants);&lt;br /&gt;use Imager;&lt;br /&gt;&lt;br /&gt;my $worker = Gearman::XS::Worker-&gt;new;&lt;br /&gt;$worker-&gt;add_server( 'localhost', 4730 );&lt;br /&gt;&lt;br /&gt;$worker-&gt;add_function( "convert_to_jpeg", 0, \&amp;convert_to_jpeg, {} );&lt;br /&gt;$worker-&gt;add_function( "convert_to_gif",  0, \&amp;convert_to_gif,  {} );&lt;br /&gt;&lt;br /&gt;while (1) {&lt;br /&gt;    my $ret = $worker-&gt;work();&lt;br /&gt;    if ( $ret != GEARMAN_SUCCESS ) {&lt;br /&gt;        printf( STDERR "%s\n", $worker-&gt;error() );&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub convert_to_jpeg {&lt;br /&gt;    my ($job) = @_;&lt;br /&gt;    return _convert( $job-&gt;workload, 'jpeg' );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub convert_to_gif {&lt;br /&gt;    my ($job) = @_;&lt;br /&gt;    return _convert( $job-&gt;workload, 'gif' );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub _convert {&lt;br /&gt;    my ( $in_data, $format ) = @_;&lt;br /&gt;    my $img = Imager-&gt;new();&lt;br /&gt;    my $out_data;&lt;br /&gt;    $img-&gt;read( data =&gt; $in_data ) or die;&lt;br /&gt;    $img-&gt;write( data =&gt; \$out_data, type =&gt; $format ) or die;&lt;br /&gt;    return $out_data;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The problem in this design is to have two functions in the same script. If you depend on running convert_to_jpeg and convert_to_gif at the same time you could split up the script:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;#!/usr/bin/env perl&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use Gearman::XS::Worker;&lt;br /&gt;use Gearman::XS qw(:constants);&lt;br /&gt;use Imager;&lt;br /&gt;&lt;br /&gt;my $worker = Gearman::XS::Worker-&gt;new;&lt;br /&gt;$worker-&gt;add_server( 'localhost', 4730 );&lt;br /&gt;&lt;br /&gt;$worker-&gt;add_function( "convert_to_jpeg", 0, \&amp;convert_to_jpeg, {} );&lt;br /&gt;&lt;br /&gt;while (1) {&lt;br /&gt;    my $ret = $worker-&gt;work();&lt;br /&gt;    if ( $ret != GEARMAN_SUCCESS ) {&lt;br /&gt;        printf( STDERR "%s\n", $worker-&gt;error() );&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub convert_to_jpeg {&lt;br /&gt;    my ($job) = @_;&lt;br /&gt;    return _convert( $job-&gt;workload, 'jpeg' );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub _convert {&lt;br /&gt;    my ( $in_data, $format ) = @_;&lt;br /&gt;    my $img = Imager-&gt;new();&lt;br /&gt;    my $out_data;&lt;br /&gt;    $img-&gt;read( data =&gt; $in_data ) or die;&lt;br /&gt;    $img-&gt;write( data =&gt; \$out_data, type =&gt; $format ) or die;&lt;br /&gt;    return $out_data;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;#!/usr/bin/env perl&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;use Gearman::XS::Worker;&lt;br /&gt;use Gearman::XS qw(:constants);&lt;br /&gt;use Imager;&lt;br /&gt;&lt;br /&gt;my $worker = Gearman::XS::Worker-&gt;new;&lt;br /&gt;$worker-&gt;add_server( 'localhost', 4730 );&lt;br /&gt;&lt;br /&gt;$worker-&gt;add_function( "convert_to_gif", 0, \&amp;convert_to_gif, {} );&lt;br /&gt;&lt;br /&gt;while (1) {&lt;br /&gt;    my $ret = $worker-&gt;work();&lt;br /&gt;    if ( $ret != GEARMAN_SUCCESS ) {&lt;br /&gt;        printf( STDERR "%s\n", $worker-&gt;error() );&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub convert_to_gif {&lt;br /&gt;    my ($job) = @_;&lt;br /&gt;    return _convert( $job-&gt;workload, 'gif' );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub _convert {&lt;br /&gt;    my ( $in_data, $format ) = @_;&lt;br /&gt;    my $img = Imager-&gt;new();&lt;br /&gt;    my $out_data;&lt;br /&gt;    $img-&gt;read( data =&gt; $in_data ) or die;&lt;br /&gt;    $img-&gt;write( data =&gt; \$out_data, type =&gt; $format ) or die;&lt;br /&gt;    return $out_data;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The next problem is about memory: Having many scripts running in separate processes using the same (sometimes heavyweight) library is not very smart. And especially in this example where images are processed those separate processes may consume a lot of ram after a long runtime. So it would be cool to share the memory consumed by libraries as well as freeing up memory from time to time. This is where &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; comes in handy. It's not only sharing memory by using the advantages of &lt;a href="http://en.wikipedia.org/wiki/Copy-on-write"&gt;copy-on-write&lt;/a&gt; but it's also having other cool features like restarting worker processes after some idle time to free up memory. Besides that it got some telnet interface to change amount of processes on runtime as well as getting some statistics et cetera.&lt;br /&gt;&lt;br /&gt;So let's re-implement the example above:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;package GDExamples::Convert;&lt;br /&gt;&lt;br /&gt;use base qw(Gearman::Driver::Worker);&lt;br /&gt;use Moose;&lt;br /&gt;use Imager;&lt;br /&gt;&lt;br /&gt;sub process_name {&lt;br /&gt;    my ( $self, $orig, $job_name ) = @_;&lt;br /&gt;    return "$orig ($job_name)";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub convert_to_jpeg : Job : MinProcesses(0) : MaxProcesses(5) {&lt;br /&gt;    my ( $self, $job, $workload ) = @_;&lt;br /&gt;    return _convert( $workload, 'jpeg' );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub convert_to_gif : Job : MinProcesses(0) : MaxProcesses(5) {&lt;br /&gt;    my ( $self, $job, $workload ) = @_;&lt;br /&gt;    return _convert( $workload, 'gif' );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sub _convert {&lt;br /&gt;    my ( $in_data, $format ) = @_;&lt;br /&gt;    my $img = Imager-&gt;new();&lt;br /&gt;    my $out_data;&lt;br /&gt;    $img-&gt;read( data =&gt; $in_data ) or die;&lt;br /&gt;    $img-&gt;write( data =&gt; \$out_data, type =&gt; $format ) or die;&lt;br /&gt;    return $out_data;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To run that worker you can use the script called &lt;strong&gt;gearman_driver.pl&lt;/strong&gt; which is part of the &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; distribution.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: plain"&gt;&lt;br /&gt;usage: gearman_driver.pl [long options...]&lt;br /&gt;--loglevel           Log level (default: INFO)&lt;br /&gt;--lib                Example: --lib ./lib --lib /custom/lib&lt;br /&gt;--max_idle_time      How many seconds a worker may be idle before its killed&lt;br /&gt;--server             Gearman host[:port][,host[:port]]&lt;br /&gt;--logfile            Path to logfile (default: gearman_driver.log)&lt;br /&gt;--console_port       Port of management console (default: 47300)&lt;br /&gt;--interval           Interval in seconds (see Gearman::Driver::Observer)&lt;br /&gt;--loglayout          Log message layout (default: [%d] %p %m%n)&lt;br /&gt;--namespaces         Example: --namespaces My::Workers --namespaces My::OtherWorkers&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Run the example: &lt;strong&gt;gearman_driver.pl --namespaces GDExamples &amp;amp;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;You can easily test the workers using the gearman client:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;gearman -f GDExamples::Convert::convert_to_jpeg &lt;&gt; cpan.jpg&lt;/li&gt;&lt;br /&gt;&lt;li&gt;gearman -f GDExamples::Convert::convert_to_gif &lt;&gt; cpan.gif&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Now let's see what we did. First of all you may have noticed the attributes on both methods:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;sub convert_to_jpeg : &lt;strong&gt;Job&lt;/strong&gt; : MinProcesses(0) : MaxProcesses(5) {}&lt;/li&gt;&lt;br /&gt;&lt;li&gt;sub convert_to_jpeg : Job : &lt;strong&gt;MinProcesses(0)&lt;/strong&gt; : MaxProcesses(5) {}&lt;/li&gt;&lt;br /&gt;&lt;li&gt;sub convert_to_jpeg : Job : MinProcesses(0) : &lt;strong&gt;MaxProcesses(5)&lt;/strong&gt; {}&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Basically you only need to add the attribute &lt;strong&gt;Job&lt;/strong&gt; to the methods which should be registered with gearmand. All other attributes are optional. Your method will be registered with the full package name:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;GDExamples::Convert::convert_to_jpeg&lt;/li&gt;&lt;br /&gt;&lt;li&gt;GDExamples::Convert::convert_to_gif&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;If you don't like that behaviour you can override &lt;strong&gt;prefix&lt;/strong&gt;. By default it's implemented this way:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: perl"&gt;&lt;br /&gt;sub prefix {&lt;br /&gt;    return ref(shift) . '::';&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;There are other predefined methods which can be overridden, like &lt;strong&gt;begin&lt;/strong&gt; or &lt;strong&gt;end&lt;/strong&gt;. Both are called whenever a job function is called, &lt;strong&gt;begin&lt;/strong&gt; before the job method is called and &lt;strong&gt;end&lt;/strong&gt; afterwards (even if the job method died).&lt;br /&gt;&lt;br /&gt;The other two attributes tell &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; how many processes should be forked and work on that function/job. If you set &lt;strong&gt;MinProcesses&lt;/strong&gt; to 0 no process is preforked at all. It's being forked on demand: &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; monitors the &lt;a href="http://search.cpan.org/dist/Net-Telnet-Gearman/"&gt;telnet interface&lt;/a&gt; of gearmand for queued jobs to see if it's necessary to fork a new process. If there are many jobs in the queue it will fork even more processes until &lt;strong&gt;MaxProcesses&lt;/strong&gt; is reached. After all jobs are done it will kill all processes again. The default behaviour is to do that immediately, but can be changed by setting &lt;strong&gt;max_idle_time&lt;/strong&gt; to 300 (seconds) for example.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; itself got a &lt;a href="http://search.cpan.org/dist/Gearman-Driver/lib/Gearman/Driver/Console.pm"&gt;telnet interface&lt;/a&gt; as well. There's also a client coming with the distribution having readline support. Another feature is to connect to multiple servers at once to send the same commands to all servers. Really handy in a big environment.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: plain"&gt;&lt;br /&gt;gearman_driver_console.pl --server localhost:47300&lt;br /&gt;console&gt; status&lt;br /&gt;localhost:47300&gt; GDExamples::Convert::convert_to_gif   0  5  0  2010-01-30T19:45:43  1970-01-01T00:00:00&lt;br /&gt;localhost:47300&gt; GDExamples::Convert::convert_to_jpeg  0  5  0  2010-01-30T19:45:53  1970-01-01T00:00:00&lt;br /&gt;localhost:47300&gt; .&lt;br /&gt;console&gt; set_processes GDExamples::Convert::convert_to_gif 2 10&lt;br /&gt;localhost:47300&gt; OK&lt;br /&gt;localhost:47300&gt; .&lt;br /&gt;console&gt; status&lt;br /&gt;localhost:47300&gt; GDExamples::Convert::convert_to_gif   2  10  2  2010-01-30T19:45:43  1970-01-01T00:00:00&lt;br /&gt;localhost:47300&gt; GDExamples::Convert::convert_to_jpeg  0   5  0  2010-01-30T19:45:53  1970-01-01T00:00:00&lt;br /&gt;localhost:47300&gt; .&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I've just shipped v0.01017 to the &lt;a href="http://www.cpan.org/"&gt;CPAN&lt;/a&gt; containing the examples in this blog post. If you can't wait for it, you can fetch it from &lt;a href="http://github.com/plu/gearman-driver"&gt;GitHub&lt;/a&gt;. Any comments/suggestions/rants will be highly appreciated. Thanks.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-7412633010196908549?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.pqpq.de/feeds/7412633010196908549/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.pqpq.de/2010/01/gearmandriver.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/7412633010196908549'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4934018258162745792/posts/default/7412633010196908549'/><link rel='alternate' type='text/html' href='http://www.pqpq.de/2010/01/gearmandriver.html' title='Gearman::Driver'/><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/07403140829864430702</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='10374274144747074890'/></author><thr:total>0</thr:total></entry></feed>