PHP Dictionary Stored in Database | ja

Fontaine でよく使っているクラスを紹介する。データベースのテーブルを単に連想配列として扱う PHP クラスだ。トランザクション処理はしないので、直接データベース・アダプタからトランザクション処理をする必要はある。あまりに使いすぎてるため、致命的な欠陥があれば指摘していただきたい。(というのも値の取得時に同時アクセスがあったときが怪しい。)

入力値のサニテーションは Zend_Db_Adapter_Abstract が行うことを前提にしている。

例えば次のようなテーブルを考える。

CREATE TABLE IF NOT EXISTS dictionary (
    name  TEXT PRIMARY KEY,
    value TEXT
);

そのテーブルをこう扱うことができる。

<?php

// Given $db
$dictionary = new Fontaine_Db_Dictionary(array(
    'db'            => $db,
    'name'          => 'dictionary',
    'primary'       => 'name',
    'valueColumn'   => 'value'
));

$adapter = $dictionary->getAdapter();
$adapter->beginTransaction();
try {
    $dictionary->keyForStringValue  = 'string';
    $dictionary->keyForBooleanValue = true;
    $dictionary->keyForIntegerValue = 0xffff;
    $dictionary->keyForFloatValue   = M_PI;
    $dictionary->keyForArrayValue   = array('value1', 'value2');
    $adapter->commit();
    
} catch (Exception $e) {
    $adapter->rollBack();
    // TODO: Deal with error
}

// Output: string(6) "string"
var_dump($dictionary->keyForStringValue);
// Output: bool(true)
var_dump($dictionary->keyForBooleanValue);
// Output: int(65535)
var_dump($dictionary->keyForIntegerValue);
// Output: float(3.1415926535898)
var_dump($dictionary->keyForFloatValue);
// Output: array(2) { [0]=> string(6) "value1" [1]=> string(6) "value2" }
var_dump($dictionary->keyForArrayValue);

以下、クラス定義全文。Fontaine_Db_Dictionary_Interface はヘッダファイルの無い PHP のクラス定義を明確にするためのインタフェースだ。Fontaine_Db_Dictionary_Interface は ArrayAccess を継承している。

<?php
/**
 * Fontaine Library
 * 
 * @category  Fontaine
 * @package   Fontaine_Db
 * @author    Matsuda Shota
 * @copyright (c) 2010 Matsuda Shota
 * @license   http://www.opensource.org/licenses/bsd-license.php
 */


/**
 * @see Fontaine_Db_Dictionary_Interface
 */
require_once 'Fontaine/Db/Dictionary/Interface.php';


/**
 * @see Zend_Db_Table_Abstract
 */
require_once 'Zend/Db/Table/Abstract.php';


/**
 * @category  Fontaine
 * @package   Fontaine_Db
 * @author    Matsuda Shota
 * @copyright (c) 2010 Matsuda Shota
 * @license   http://www.opensource.org/licenses/bsd-license.php
 */
class Fontaine_Db_Dictionary
extends Zend_Db_Table_Abstract
implements Fontaine_Db_Dictionary_Interface
{
    /**
     * @var string
     */
    const VALUE_COLUMN = 'valueColumn';
    
    /**
     * @var string
     */
    protected $_valueColumn = null;
    
    /**
     * @param   array $config
     * @return  void
     * @see     Zend_Db_Table_Abstract::__construct()
     */
    public function __construct($config)
    {
        parent::__construct($config);
    }
    
    /**
     * @param   array $options
     * @return  Zend_Db_Table_Abstract
     * @see     Zend_Db_Table_Abstract::setOptions()
     */
    public function setOptions(array $options)
    {
        foreach ($options as $key => $value) {
            switch ($key) {
                case self::VALUE_COLUMN:
                    $this->_valueColumn = $value;
                    break;
            }
        }
        return parent::setOptions($options);
    }
    
    /**
     * @return  void
     * @throws  Fontaine_Db_Exception
     */
    protected function _setupValueColumn()
    {
        if ($this->_valueColumn === null) {
            require_once 'Fontaine/Db/Exception.php';
            throw new Fontaine_Db_Exception("The name of the value column does not specified.");
        }
        $tableDescription = $this->getAdapter()->describeTable($this->_name);
        if (!isset($tableDescription[$this->_valueColumn])) {
            require_once 'Fontaine/Db/Exception.php';
            throw new Fontaine_Db_Exception("The value column \"{$this->_valueColumn}\" does not exist.");
        }
    }
    
    /**
     * @param   string $primaryKey
     * @return  Zend_Db_Table_Row|null
     * @throws  Fontaine_Db_Exception
     */
    private function _fetchSingleRow($primaryKey)
    {
        $fetchedRow  = null;
        $rowset      = $this->find($primaryKey);
        $rowsetCount = $rowset->count();
        
        if ($rowsetCount == 1) {
            $fetchedRow = $rowset->current();
        
        } else if ($rowsetCount > 1) {
            require_once 'Fontaine/Db/Exception.php';
            throw new Fontaine_Db_Exception("Detected violation error -- found multiple rows corresponding to the given primary key: $primaryKey");
        }
        
        // This returns null when this dictionary does not have the given key
        return $fetchedRow;
    }
    
    /**
     * @return  string
     * @throws  Fontaine_Db_Exception
     */
    private function _primaryColumn()
    {
        // Check if we have multiple primary key columns
        $primaryCount = count($this->_primary);
        if ($primaryCount > 1) {
            require_once 'Fontaine/Db/Exception.php';
            throw new Fontaine_Db_Exception("A dictionary table should have only one primary key. $primaryCount found.");
        }
        
        return reset($this->_primary);
    }
    
    /**
     * @param   string $value
     * @return  mixed
     * @throws  Fontaine_Db_Exception
     */
    protected function _wakeupValue($value)
    {
        $unserializedValue = @unserialize($value);
        if ($unserializedValue === null) {
            require_once 'Fontaine/Db/Exception.php';
            throw new Fontaine_Db_Exception("Unable to wakeup the given value.");
        }
        return $unserializedValue;
    }
    
    /**
     * @param   mixed $value
     * @return  string
     * @throws  Fontaine_Db_Exception
     */
    protected function _sleepValue($value)
    {
        if (is_resource($value)) {
            require_once 'Fontaine/Db/Exception.php';
            throw new Fontaine_Db_Exception("Unable to sleep the value -- resource type of value given.");
        }
        if (is_null($value)) {
            require_once 'Fontaine/Db/Exception.php';
            throw new Fontaine_Db_Exception("Unable to sleep the value -- null value given.");
        }
        
        $serializedValue = @serialize($value);
        if ($serializedValue === null) {
            require_once 'Fontaine/Db/Exception.php';
            throw new Fontaine_Db_Exception("Unable to sleep the given value.");
        }
        return $serializedValue;
    }
    
    /**
     * @param   string $key
     * @return  mixed|null
     * @see     Fontaine_Db_Dictionary_Interface::get()
     */
    public function get($key)
    {
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        $value = null;
        $fetchedRow = $this->_fetchSingleRow($key);
        if ($fetchedRow) {
            $value = $this->_wakeupValue($fetchedRow[$this->_valueColumn]);
        }
        
        // This returns null when this dictionary does not have the given key
        return $value;
    }
    
    /**
     * @param   string $key
     * @param   mixed $value
     * @return  Fontaine_Db_Dictionary_Interface
     * @see     Fontaine_Db_Dictionary_Interface::set()
     */
    public function set($key, $value)
    {
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        $fetchedRow = $this->_fetchSingleRow($key);
        if (!$fetchedRow) {
            // Create a new row with the given key as our primary key
            $primaryColumn = $this->_primaryColumn();
            $fetchedRow = $this->createRow();
            $fetchedRow[$primaryColumn] = $key;
        }
        
        $fetchedRow[$this->_valueColumn] = $this->_sleepValue($value);
        $fetchedRow->save();
        
        return $this;
    }
    
    /**
     * @param   array|Traversable $dictionary
     * @return  Fontaine_Db_Dictionary_Interface
     * @throws  Fontaine_Db_Exception
     * @see     Fontaine_Db_Dictionary_Interface::setAll()
     */
    public function setAll($dictionary)
    {
        if (!is_array($dictionary) && !$dictionary instanceof Traversable) {
            require_once 'Fontaine/Db/Exception.php';
            throw new Fontaine_Db_Exception("The argument should be traversable.");
        }
        
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        foreach ($dictionary as $eachKey => $eachValue) {
            $fetchedRow = $this->_fetchSingleRow($eachKey);
            if (!$fetchedRow) {
                // Create a new row with the given key as our primary key
                $primaryColumn = $this->_primaryColumn();
                $fetchedRow = $this->createRow();
                $fetchedRow[$primaryColumn] = $eachKey;
            }
            
            $fetchedRow[$this->_valueColumn] = $this->_sleepValue($eachValue);
            $fetchedRow->save();
        }
        
        return $this;
    }
    
    /**
     * @param   string $key
     * @return  boolean
     * @see     Fontaine_Db_Dictionary_Interface::hasKey()
     */
    public function hasKey($key)
    {
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        return $this->_fetchSingleRow($key) !== null;
    }
    
    /**
     * @param   array|Traversable $keys
     * @return  boolean
     * @throws  Fontaine_Db_Exception
     * @see     Fontaine_Db_Dictionary_Interface::hasAllKeys()
     */
    public function hasAllKeys($keys)
    {
        if (!is_array($keys) && !$keys instanceof Traversable) {
            require_once 'Fontaine/Db/Exception.php';
            throw new Fontaine_Db_Exception("The argument should be traversable.");
        }
        
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        foreach ($keys as $eachKey) {
            if (!$this->_fetchSingleRow($eachKey)) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * @param   string $key
     * @return  Fontaine_Db_Dictionary_Interface
     * @see     Fontaine_Db_Dictionary_Interface::remove()
     */
    public function remove($key)
    {
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        $fetchedRow = $this->_fetchSingleRow($key);
        if ($fetchedRow) {
            $fetchedRow->delete();
        }
        
        return $this;
    }
    
    /**
     * @param   array|Traversable|null $keys
     * @return  Fontaine_Db_Dictionary_Interface
     * @throws  Fontaine_Db_Exception
     * @see     Fontaine_Db_Dictionary_Interface::removeAll()
     */
    public function removeAll($keys = null)
    {
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        if ($keys === null) {
            $fetchedRows = $this->fetchAll();
            foreach ($fetchedRows as $eachRow) {
                $eachRow->delete();
            }
        } else {
            if (!is_array($keys) && !$keys instanceof Traversable) {
                require_once 'Fontaine/Db/Exception.php';
                throw new Fontaine_Db_Exception("The argument should be traversable.");
            }
            
            foreach ($keys as $eachKey) {
                $fetchedRow = $this->_fetchSingleRow($eachKey);
                if ($fetchedRow) {
                    $fetchedRow->delete();
                }
            }
        }
        
        return true;
    }
    
    /**
     * @return  boolean
     * @see     Fontaine_Db_Dictionary_Interface::isEmpty()
     */
    public function isEmpty()
    {
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        return $this->fetchAll()->count() == 0;
    }
    
    /**
     * @return  array
     * @see     Fontaine_Db_Dictionary_Interface::toValueArray()
     */
    public function toValueArray()
    {
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        $resultArray   = array();
        $primaryColumn = $this->_primaryColumn();
        $fetchedRowset = $this->fetchAll();
        
        foreach ($fetchedRowset as $eachRow) {
            $resultArray[] = $this->_wakeupValue($eachRow[$this->_valueColumn]);
        }
        
        return $resultArray;
    }
    
    /**
     * @return  array
     * @see     Fontaine_Db_Dictionary_Interface::toKeyArray()
     */
    public function toKeyArray()
    {
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        $resultArray   = array();
        $primaryColumn = $this->_primaryColumn();
        $fetchedRowset = $this->fetchAll();
        
        foreach ($fetchedRowset as $eachRow) {
            $resultArray[] = $eachRow[$primaryColumn];
        }
        
        return $resultArray;
    }
    
    /**
     * @return  array
     * @see     Fontaine_Db_Dictionary_Interface::toArray()
     */
    public function toArray()
    {
        $this->_setupPrimaryKey();
        $this->_setupValueColumn();
        
        $resultArray   = array();
        $primaryColumn = $this->_primaryColumn();
        $fetchedRowset = $this->fetchAll();
        
        foreach ($fetchedRowset as $eachRow) {
            $eachKey   = $eachRow[$primaryColumn];
            $eachValue = $this->_wakeupValue($eachRow[$this->_valueColumn]);
            $resultArray[$eachKey] = $eachValue;
        }
        
        return $resultArray;
    }
    
    /**
     * @param   string $name
     * @return  boolean
     * @see     Fontaine_Db_Dictionary_Interface::__isset()
     * @uses    Fontaine_Db_Dictionary_Interface::hasKey()
     */
    public function __isset($name)
    {
        return $this->hasKey($name);
    }
    
    /**
     * @param   string $name
     * @return  mixed
     * @see     Fontaine_Db_Dictionary_Interface::__get()
     * @uses    Fontaine_Db_Dictionary_Interface::get()
     */
    public function __get($name)
    {
        return $this->get($name);
    }
    
    /**
     * @param   string $name
     * @param   mixed $value
     * @return  mixed
     * @see     Fontaine_Db_Dictionary_Interface::__set()
     * @uses    Fontaine_Db_Dictionary_Interface::set()
     */
    public function __set($name, $value)
    {
        $this->set($name, $value);
        return $value;
    }
    
    /**
     * @param   string $name
     * @see     Fontaine_Db_Dictionary_Interface::__unset()
     * @uses    Fontaine_Db_Dictionary_Interface::remove()
     */
    public function __unset($name)
    {
        $this->remove($name);
    }
    
    /**
     * @param   string $offset
     * @return  boolean
     * @see     ArrayAccess::offsetExists()
     * @uses    Fontaine_Db_Dictionary_Interface::hasKey()
     */
    public function offsetExists($offset)
    {
        return $this->hasKey($offset);
    }
    
    /**
     * @param   string $offset
     * @return  mixed
     * @see     ArrayAccess::offsetGet()
     * @uses    Fontaine_Db_Dictionary_Interface::get()
     */
    public function offsetGet($offset)
    {
        return $this->get($offset);
    }
    
    /**
     * @param   string $offset
     * @param   mixed $value
     * @return  mixed
     * @see     ArrayAccess::offsetSet()
     * @uses    Fontaine_Db_Dictionary_Interface::set()
     */
    public function offsetSet($offset, $value)
    {
        $this->set($offset, $value);
        return $value;
    }
    
    /**
     * @param   string $offset
     * @see     ArrayAccess::offsetUnset()
     * @uses    Fontaine_Db_Dictionary_Interface::remove()
     */
    public function offsetUnset($offset)
    {
        $this->remove($offset);
    }
}

Posted in Programming | Tagged , | Leave a comment

Seriette: A Serial Port Proxy Application for Mac OS X | ja

Mac OS X で動作するシリアルプロキシ Seriette を紹介します。シリアルポートを操作できない Flash などから、RS232 で接続された(あるいはそのように認識される)シリアル通信デバイスを TCP ソケットを使って制御することができます。また、TCP/IP スタックを持たない安価なデバイスとネットワーク経由でシリアル通信を行えるようになります。

現在は開発プレビュー段階です。主要な機能は実装済みです。あと一歩作り込んだのちにコードも公開する予定です。

追記:なかなか時間が取れないので、現状のままソースコード公開しておきます。
http://github.com/sgss/Seriette

特徴

  • 複数のシリアルポートとの接続およびプロキシサーバの実行
  • マルチスレッド化された非同期通信プロセス
  • フロー制御などの詳細なシリアル通信機能

開発プレビューの入手

» Download “Seriette” Preview Release

動作環境

Mac OS X 10.6 Snow Leopard

使用しているサードパーティ・フレームワークとライブラリ

Flash でのシリアル通信

Flash からシリアルポートを操作するには、何かしらの仲介役、プロキシが必要になります。具体的には flash.net.XMLSocketflash.net.Socket を使って、シリアルポートを制御するプロキシと通信することになります。プロキシはネットワークからデータを受け取ると、対応するデータを直接シリアルポートへ送信します。シリアルポートから受信したときは反対の流れです。

さてこのプロキシですが、Windows や Linux には選択肢色々とあります。ところが Mac OS X で動作するものは知る限り serproxy という CUI アプリケーションだけです(不幸にも serproxy はこの記事の執筆中に発見)。

Seriette はこのプロキシ・アプリケーションの一種になります。

Seriette の使いかた

使いかたを書かなければならないのが開発プレビューである理由です。

  1. サービスの追加
  2. シリアルポートの接続
  3. TCP サーバーの起動

サービスの追加

左下のプラスボタンを押して、管理するシリアルポートのサービスを追加しましょう。

ステータスが “Available” である項目が、現在実際に接続できる状態にあるサービスです。“Unknown” はサービスはあるけれど接続可能かどうか不確かなものです。

シリアルポートの接続

ソースリストから接続するサービスを選択して、設定しましょう。黒いクイックバー上でボーレートデータフレームを変更できます。また、“More Options” ボタンからフロー制御などのより詳細な設定へアクセスできます。

ソースリストの項目を右クリックして出てくるコンテキストメニューから “Connect” を選びます。Connect/Disconnect ボタン(否、むしろアイコン)はいずれ作ります。

クイックバーが緑色になれば無事に接続完了です。

TCP サーバーの起動

手順はシリアルポートと同様です。ツールバー上の “TCP Server” をクリックし、サーバを設定します。クイックバーの “Interface” ポップアップには利用可能なホストの IP アドレスと予約語が一覧されています。恐らくここでは “More Options” を覗いておいたほうが良いでしょう。

デフォルトでは “Manually allow incoming connection” がオンになっていることを確認しておいて下さい。意味は注釈の通りです。

ソースリストの項目を右クリックして出てくるコンテキストメニューから “Run TCP Server” を選択して完了です。これも同じく Run/Stop ボタンを後々作ります。

Seriette の TCP サーバは、相手ソケットとのハンドシェイクを終えればその IP アドレスをリストに追加します。“Manually allow incoming connection” がオンのとき、“Allow” をしなければいかなるソケット通信も処理されないことに注意して下さい。

おわりに

Mac OS X で動作するシリアルサーバ “Seriette” の開発プレビューを紹介しました。まだ一度もシリアル通信をしたことがない方にはイメージが沸かないかもしれませんが、Seriette を使えば Mac OS X 上でのシリアル通信を使った電子工作および開発が少し楽になります。時間を確保できればサンプル・プロジェクトを紹介する予定です。また試用してみてのバグ報告や要望もお待ちしています。

Posted in Featured, Software | Tagged , , , | Leave a comment

Route Virtual Host to Local WordPress | ja

これは私の備忘録と言うより、要望への返答だ。

目標と結果

次のような URL に WordPress が設置されているとする。

これを次の URL でも解決(resolve)されるようにする。

こうすることで、WordPress とローカルで砂遊びしたあとリモートサーバへ移行するときに、データベース内の変更すべきパスをほぼ無くすことができる。

具体的な手順と説明

WordPress の設置

/Users/username/Sites/ の中に下図のようなディレクトリ構造を考える。ディレクトリ “wordpress” には WordPress がインストールされていてdomainname はあなたの持っているネームドメインだ。ディレクトリ構造自体は本質ではないが、ルーティングするドメインを別ディレクトリに分けておく(この場合 “virtual”)ことと、依存関係のない隔離されたライブラリも同じく分けておく(この場合 “static”)ことは、管理方針を明確にする点で大切。この “static” は “virtual” の直下に置くこともある。その場合複数のドメインで共通のライブラリを使うという方針を立てることになる。“media” はアップロードするファイルの入れ物。

さて、index.php は WordPress に付属しているやつを移動したものだ。これがきちんと WordPress 自体を読み込めるように変更を加えよう。index.php から wp-blog-header.php までのパスを渡してやる。このあたりの話は Giving WordPress Its Own Directory に詳しい。

/** Loads the WordPress Environment and Template */
require('static/wordpress/wp-blog-header.php');

バーチャルホストとルーティングの設定

Mac OS X にあらかじめ入っている Apache よりも MacPorts からインストールした Apache の方が保守点検が楽だ。以下 MacPorts からインストールした Apache 2.0 を想定している。

domainname がローカルの Apache へ届くようにするために、/etc/hosts に 1 文を追加しよう。

$ sudo /Applications/TextEdit.app/Contents/MacOS/TextEdit /etc/hosts
127.0.0.1 domainname

これで domainname はループバックアドレス 127.0.0.1、つまり自分自身を示すように IP アドレスが解決される。例えば http://domainname/ は 127.0.0.1:80 へと解決され、ローカルの Apache が受け取けとれることになる。

さて、次はリクエストを受け取る Apache 側の設定だ。バーチャルホストの設定はどこに書いてもいいのだけども、MacPorts から Apache をインストールしたなら、/opt/local/apache2/conf/extra/httpd-vhosts.conf というファイルが用意されているはず。この設定ファイルを httpd.conf から読み込むのが理にかなってるだろう。/opt/local/apache2/conf/httpd.conf に 1 文を加えよう(あるいはコメントアウトかな?)。

# Virtual hosts
Include conf/extra/httpd-vhosts.conf

それから httpd-vhosts.conf を変更する。あらかじめ書かれているコードは削除するかコメントアウトしても構わない。

# Ensure that Apache listens on port 80
Listen 80

# Listen for virtual host requests on all IP addresses
NameVirtualHost *:80

<VirtualHost *:80>
    ServerName domainname
    DocumentRoot /Users/username/Sites/virtual/domainname
    <Directory "/Users/username/Sites/virtual/domainname">
        Order deny, allow
        Allow from all
        # Other directives here
    </Directory>
</VirtualHost>

もちろんほとんどの場合正式なドキュメンテーションはある。面倒くさくなければこんな記事を見るよりもずっと役に立つはずだ。

さて、変更を適用するために Apache を再起動してこの節は終わり(Shell のパスはちゃんと通ってる?)。

$ sudo apachectl graceful

ちなみにバーチャルホストを作りすぎて混乱したときは一覧を見るといい。

$ httpd -S

WordPress の設定

以上で http://domainname/static/wordpress/wp-admin/ から WordPress の管理画面を開けるはずだ。設定すべきはパスの設定だけ。まずは General Settings。

次に Miscellaneous の中のアップロードするファイルの保存場所と URL の接頭辞。

最後に、ファイルにもディレクトリにもマッチしないすべての URL が index.php へと繋がるように .htaccess を編集しておしまい。

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.php [L]
</IfModule>

まとめ

要するにやるべきことはパスの設定に一貫性を持たせることだけで、とても簡単だ。こまかいこと(たとえば IP アドレスの意味であるとか mod_rewrite の挙動とか)はそれぞれ調べる必要はあるだろうけれど、大局的に難しいところはひとつもない。それだけで作業の効率が上がるかもしれないとなれば、多からず悩む価値はあるだろう。

Posted in Uncategorized | Tagged , | Leave a comment

Interactively Capture Screen | en

While there are many ways to take screenshot, when you want to capture screen in the same way as Grab.app (or Command-Shift-3 or the like) does, using NSTask to launch “/usr/sbin/screencapture” might be the easiest way.

We can use the paste board for a temporal storage in which the screencapture saves captured image data, but I think users don’t expect the paste board to be modified in any operation without declaring to do so. Fortunately the previous entry covers how to make temporary files, so all you have to do is to launch the screencapture using NSTask and store its data in a temporary file, then delete it when you no longer need it.

A generic code to launch the screencapture could be like:

- (void)runScreenCaptureWithOption:(NSString *)option
{
    NSString *launchPath = @"/usr/sbin/screencapture";
    NSString *filePath   = [self temporaryFilePathWithPathExtension:nil];
    
    
    NSAssert(filePath != nil,
             @"Error with creating temporary file path");
    
    
    NSMutableArray *arguments = [NSMutableArray array];
    if (option) {
        [arguments addObject:option];
    }
    [arguments addObject:filePath];
    
    
    // This is a task of the capturescreen with capture mode option and 
    // temporary file path as its arguments
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:launchPath];
    [task setArguments:arguments];
    
    
    // Notification for the task termination
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(screenCaptureTaskDidTerminate:)
                                                 name:NSTaskDidTerminateNotification
                                               object:task];
    
    // Launch the task
    [task launch];
    
    // The task should be released by the notification observer
}

A notification must be posted when the task terminates. We should handle in a observer method the temporary file that might stores captured image data. It would be like:

- (void)screenCaptureTaskDidTerminate:(NSNotification *)aNotification
{
    NSTask *task = [aNotification object];
    NSString *filePath = [[task arguments] lastObject];
    NSError *error;
    NSData *imageData = [NSData dataWithContentsOfFile:filePath
                                               options:NSDataReadingUncached
                                                 error:&error];
    
    if (imageData) {
        // TODO: Do something with the imageData
        
        
        // We are deleting the temporary file
        NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
        
        // The temporary file is comfirmed to exist because the operation of
        // dataWithContentsOfFile succeeded above, thus if this occur error, it 
        // may be a critical one.
        if (![fileManager removeItemAtPath:filePath error:&error]) {
            NSDebugLog(@"Error with deleting image file: code=%d, %@", [error code], [error userInfo]);
            
            // TODO: Deal with error
            [NSApp presentError:error];
        }
    
    } else if ([error code] == 260) {
        // Temporary file was not found. This usually happens when the user
        // canceled the screencapture task by pressing escape key -- Ignore it.
        NSDebugLog(@"User canceled the screencapture task");
        
    } else {
        // We got an unexpected error
        NSDebugLog(@"Error with reading image file: code=%d, %@", [error code], [error userInfo]);
        
        // TODO: Deal with error
        [NSApp presentError:error];
    }
    
    
    // No longer needed
    [task release];
}

That’s it. When you want to bind specific capturing actions to user interface items, you could define IB actions like:

- (IBAction)captureScreen:(id)sender
{
    // No option for entire screen
    [self runScreenCaptureWithOption:nil];
}


- (IBAction)captureSelection:(id)sender
{
    // "s" for selection
    [self runScreenCaptureWithOption:@"-s"];
}


- (IBAction)captureWindow:(id)sender
{
    // "W" for window
    [self runScreenCaptureWithOption:@"-W"];
}

Good luck.

Posted in Programming | Tagged , , , , | Leave a comment

Make Temporary File Path | en

We sometime need temporary files. This is a snippet to make a path of temporary file in Cocoa. Simply uses tempnam() to make an unique file name. Returns nil when the operation failed.

- (NSString *)temporaryFilePathWithPathExtension:(NSString *)pathExtension
{
    // This should represent the path of a unique file in the dedicated 
    // temporary directory
    char *nameBytes = tempnam([NSTemporaryDirectory() fileSystemRepresentation], [[ApplicationName stringByAppendingString:@"-"] UTF8String]);
    
    // Check if it failed
    if (nameBytes == NULL) {
        NSDebugLog(@"Error with making temporary file path");
        return nil;
    }
    
    // Convert the path to NSString. No need to free the nameBytes because this
    // does it instead.
    NSString *name = [[NSString alloc] initWithBytesNoCopy:nameBytes
                                                    length:strlen(nameBytes)
                                                  encoding:NSUTF8StringEncoding 
                                              freeWhenDone:YES];
    // Check if it failed
    if (name == nil) {
        NSDebugLog(@"Error with converting temporary file path string");
        return nil;
    }
    
    
    // Append the given path extension or not
    NSString *filePath;
    if (pathExtension) {
        filePath = [name stringByAppendingFormat:@".%@", pathExtension];
        [name release];
        
    } else {
        filePath = [name autorelease];
    }
    
    return filePath;
}
Posted in Programming | Tagged , , , , | Leave a comment

Chain of Responsibility Pattern Illustration | en

Some advices I’ve received for this illustration said that it looks more like a traversal of array, not an practical process of responder chain. Typical responder implements a public field to get reference to the next responder, then, for the sake of simplicity, the process of responder chain often involves recursive calls.

I should think a way to draw recursiveness, which goes up call stack.

This work is licensed under a Creative Commons Attribution 3.0 Unported License.

Posted in Drawing, Programming | Tagged , , , , , | Leave a comment

Drawing Electronic Circuit Diagrams in Illustrator | en

I usually use Eagle Layout Editor to make schematics and wiring diagram. Although free version of this software even works enough to create professional PCB gerber data, Illustrator seems more convenient when it comes to prototyping on universal board. We can draw out-of-grid jumper wires to provide more flexibility into its design, plan the layout of soldered joints that will help us to maintain the strength of wires, and, easily make it beautiful.

This archive contains vector data I use to draw wiring diagrams on 2.54mm spaced holes.

Recommended Workspace

In “Guides & Grid” preferences, set gridline every 2.54mm and subdivision to around 4.

Then enable “Snap to Grid” from “View” menu. Every component group has transparent rectanglar bounds that fit to 1.27mm grids.

All you have to do is to place components and wire by the pen tool, but to mantain the consistency of the data so that it can be “wiring diagram” because Illustrator does not do anything every circuit layout editor does. A tip is to keep layers organized like below when you design through-hole universal board.

Good luck!

Posted in Drawing, Electronics, Featured | Tagged , , , , , | Leave a comment

Observer Pattern Illustration | en

This work is licensed under a Creative Commons Attribution 3.0 Unported License.

Posted in Drawing, Programming | Tagged , , , , , | Leave a comment