Skip to content

Programmation

wxPython (3.0.2.0) on OS X El Capitan (10.11)

Since OS X 10.11 (El Capitan), the lastest version of the available wxPython dmg (3.0.2.0) have an unsupported pkg structure. You can see an open bug at http://trac.wxwidgets.org/ticket/17203

Thanks to memoselyk@stackoverflow we can convert manually the package structure to be able to install it : http://stackoverflow.com/questions/34402303/install-wxpython-in-osx-10-11/34622956#34622956 ; you can find an updated version of the method below :

Installing wxPython-3.0.2.0 on OS X El Capitan :

# base workdir
mkdir ~/wxpython_elcapitan
cd ~/wxpython_elcapitan

# download the wxPython dmg
curl -L "http://downloads.sourceforge.net/project/wxpython/wxPython/3.0.2.0/wxPython3.0-osx-3.0.2.0-cocoa-py2.7.dmg?r=http%3A%2F%2Fwww.wxpython.org%2Fdownload.php&ts=1453708927&use_mirror=netix" -o wxPython3.0-osx-3.0.2.0-cocoa-py2.7.dmg

# mount the dmg
hdiutil attach wxPython3.0-osx-3.0.2.0-cocoa-py2.7.dmg

# copy the dmg package to the local disk
mkdir ~/wxpython_elcapitan/repack_wxpython
cd ~/wxpython_elcapitan/repack_wxpython
cp -r /Volumes/wxPython3.0-osx-3.0.2.0-cocoa-py2.7/wxPython3.0-osx-cocoa-py2.7.pkg .

# unmount the dmg
dmgdisk="$(hdiutil info | grep '/Volumes/wxPython3.0-osx-3.0.2.0-cocoa-py2.7' | awk '{ print $1; }')"
hdiutil detach ${dmgdisk}

# prepare the new package contents
mkdir ~/wxpython_elcapitan/repack_wxpython/pkg_root
cd ~/wxpython_elcapitan/repack_wxpython/pkg_root
pax -f ../wxPython3.0-osx-cocoa-py2.7.pkg/Contents/Resources/wxPython3.0-osx-cocoa-py2.7.pax.gz -z -r
cd ~/wxpython_elcapitan/repack_wxpython

# prepare the new package scripts
mkdir ~/wxpython_elcapitan/repack_wxpython/scripts
cp wxPython3.0-osx-cocoa-py2.7.pkg/Contents/Resources/preflight scripts/preinstall
cp wxPython3.0-osx-cocoa-py2.7.pkg/Contents/Resources/postflight scripts/postinstall

# delete the old package
rm -rf ~/wxpython_elcapitan/repack_wxpython/wxPython3.0-osx-cocoa-py2.7.pkg

# build the new one :
pkgbuild --root ./pkg_root --scripts ./scripts --identifier com.wxwidgets.wxpython wxPython3.0-osx-cocoa-py2.7.pkg

# put the package on Desktop, and clean workdir
mv ~/wxpython_elcapitan/repack_wxpython/wxPython3.0-osx-cocoa-py2.7.pkg ~/Desktop/
cd ~
rm -rf ~/wxpython_elcapitan

# install it ! it will ask for your password (to become superuser/root)
sudo installer -pkg ~/Desktop/wxPython3.0-osx-cocoa-py2.7.pkg -target /

# EOF

wxPython is now available on your OS X El Capitan :

$ python -c “import wx ; print wx.version()”
3.0.2.0 osx-cocoa (classic)

Using wxPython in a VirtualEnv :

OS X is now restricting screen access to a trusted list of Frameworks and binaries, and a standard VirtualEnv is not enought :

This program needs access to the screen.
Please run with a Framework build of python, and only when you are
logged in on the main display of your Mac.

Thanks to the wxPython wiki, we can find some ways to solve that :
See : http://wiki.wxpython.org/wxPythonVirtualenvOnMac

Below, a quick solution, assuming that you don’t want system packages in your virtual env :

mkdir ~/dev
cd ~/dev
virtualenv --python=python2.7 --distribute --no-site-packages --unzip-setuptools myvirtualenv
source ~/dev/myvirtualenv/bin/activate
ln -s /Library/Python/2.7/site-packages/wxredirect.pth ~/dev/myvirtualenv/lib/python2.7/site-packages/wxredirect.pth

And now you must use the system python interpreter, instead the one in your virtual env, you should use a script, a wrapper, or modify activate script, but the minimum is :

PYTHONHOME="/Users/davixx/dev/myvirtualenv/" /usr/bin/python -c "import wx ; app = wx.App()"

Have a nice day with Mercy – MUSE

mydumper-0.6 views dump and restore

Hi readers, i hope you are listening to the MUSE Drones Album,

The mydumper-0.6.2 (the current stable version) is not dumping views… Thankfully, the 0.9 branch (not stable at now) will… (MyDumper – Add support for all schema objects)

The question is, how to safely backup theses views (without using a beta version of mydumper) ?

I search into the myloader.c source code how the regular tables have theirs schema restored, and luckily, in the following function :

void restore_data(MYSQL *conn, char *database, char *table, const char *filename, gboolean is_schema);

we can see that the content of the schema dump file is ready and executed without special verification :

// [...]
if (!is_schema)
    mysql_query(conn, "START TRANSACTION");

while (eof == FALSE) {
    if (read_data(infile, is_compressed, data, &eof)) {
        // Search for ; in last 5 chars of line
        if (g_strrstr(&data->str[data->len >= 5 ? data->len - 5 : 0], ";\n")) {
            if (mysql_real_query(conn, data->str, data->len)) {
                g_critical("Error restoring %s.%s from file %s: %s", db ? db : database, table, filename, mysql_error(conn));
                errors++;
                return;
            }
            query_counter++;
            if (!is_schema &&(query_counter == commit_count)) {
                query_counter= 0;
                if (mysql_query(conn, "COMMIT")) {
                    g_critical("Error committing data for %s.%s: %s", db ? db : database, table, mysql_error(conn));
                    errors++;
                    return;
                }
                mysql_query(conn, "START TRANSACTION");
            }

            g_string_set_size(data, 0);
        }
    } else {
        g_critical("error reading file %s (%d)", filename, errno);
        errors++;
        return;
    }
}
if (!is_schema && mysql_query(conn, "COMMIT")) {
    g_critical("Error committing data for %s.%s from file %s: %s", db ? db : database, table, filename, mysql_error(conn));
    errors++;
}
// [...]

That mean we can generate – by any way we want – a fake schema-dump file, but with a query creating a view.

For that,  i first thought of patching mydumper.c ; but as mydumper-0.9 (not stable at now) already support this feature, i will prefer another solution.

I’ve made a modest python script which list view and generate files named database.view_name-schema.sql into the mydumper dump dir. With that, myloader will consider thoses files as regular table schema create statements.

Please note that i force a DROP VIEW IF EXISTS into the dump file…

#!/usr/bin/python2.7 -u

import MySQLdb
import os

# settings
MARIADB_USER = 'a_readonly_user'
MARIADB_PASS = 'a_good_password_MU5E_D3AD_IN7ID3_%!@'
MARIADB_HOST = '127.0.0.1'
MARIADB_PORT = 3306
MARIADB_SCKT = ''  # empty for TCP , or path if socket (host must be localhost)
MYDUMPER_DUMP_DIR = '/tmp'  # the path of the dump already done by mydumper

# connection to the MariaDB server
mariadb_connection = MySQLdb.connect(
    host=MARIADB_HOST,
    user=MARIADB_USER,
    passwd=MARIADB_PASS,
    port=MARIADB_PORT,
    unix_socket=MARIADB_SCKT,
    db='information_schema',
)

# creating cursors
views_list_cursor = mariadb_connection.cursor()
show_create_view_cursor = mariadb_connection.cursor()

# ask MariaDB for the views list
views_list_cursor.execute("SELECT `table_schema`, `table_name` FROM `tables` WHERE `table_type` = 'VIEW'")

for database, view in views_list_cursor:
    # and handle each results
    show_create_view_cursor.execute("SHOW CREATE VIEW `{}`.`{}`".format(database, view))
    view, create_view_stmt, character_set_client, collation_connection = show_create_view_cursor.fetchone()
    dump_file = os.path.join(MYDUMPER_DUMP_DIR, '{}.{}-schema.sql'.format(database, view))
    assert not os.path.exists(dump_file)
    with open(dump_file, 'w') as f:
        fwrite('DROP VIEW IF EXISTS `{}`.`{}`;'.format(database, view))
        fwrite('\n\n')
        f.write(create_view_stmt)
        f.write(';')

# release cursors and close the MariaDB connection
views_list_cursor.close()
show_create_view_cursor.close()
mariadb_connection.close()

Feel free to make it PEP8, or with better error handling. Actually, the return code will be 0 only if everything seems good.

i2cd : Python + Twisted + smbus

Discuter en python avec des périphériques i2c c’est déjà assez facile grace à smbus… Jusque là aucun problème, sauf quand j’ai commencé à faire des communications concurrentes. En théorie, le bus va échanger “n’importe quoi” pendant la concurrence, et donc les échanges en cours risquent d’être perturbés/invalides.

C’est effectivement le cas, mais évidement les primitives que j’avais développés détectaient l’erreur i/o renvoyée par smbus, et retentais un certains nombre de fois les mêmes opérations. Seulement, il se trouve que certains périphériques i2c (comme ceux reposant sur certains MSP430) gèrent mal le fait d’avoir vu un début de communication i2c, sans une fin de communication valide. Et du coup, certains “plantent”, n’acceptent plus de communications i2c et nécessitent un redémarrage.

J’ai alors décidé de mettre en place un modeste daemon i2cd qui aurait pour rôle de centraliser les échanges i2c, et d’apporter une interface socket TCP simple, via twisted pour permettre aux différents applicatifs de communiquer avec.

L’avantage premier de twisted est d’être un event-driven networking engine, c’est à dire qu’il va déclencher l’appel de certaines de vos méthodes en fonction d’un évènement réseau (connexion établie, données reçues, …) ; n’étant pas un professionnel de twisted, j’ai d’ailleurs découvert que par défaut, il ne fait pas d’appels concurrents à Protocol.dataReceived (évitant d’avoir à utiliser threading.Lock)

Je précise aussi que je le client va échanger avec i2cd un message constitué de plusieurs bytes, et que i2cd ne devra pas mélanger les messages lorsque il les enverra sur le bus i2c.

Le but de ce post est un PoC serveur + client, ne recherchez donc pas la compatibilité python-3, le respect de la PEP8, ou autre (mais n’hésitez pas à la proposer)…

Le client (directement avec les sockets python natives) :

#!/usr/bin/python2.7

__author__ = 'davixx'
I2CD_HOST = '127.0.0.1'
I2CD_PORT = 422

# -------------------------------------------------------------------------------------------------------------------- #

import socket
import sys
import re
import time

# -------------------------------------------------------------------------------------------------------------------- #

I2CD_WELCOME_BANNER_RE = re.compile('^200\sWELCOME\s\-\sYOU\sARE\s\d+\sIN$')
I2CD_ANSWER_RE = re.compile('^(?P<code>\d{3})\s(?P<str>.+)$')
I2CD_READED_STR_RE = re.compile('^READED\s(?P<byte>\d+)$')

# -------------------------------------------------------------------------------------------------------------------- #

def remote_msg(addr, msg, read=False):

    res = False

    try:
        # connect
        socket_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        socket_client.connect((I2CD_HOST, I2CD_PORT))

        # banner
        socket_client.send('I2CD CLIENT\n')
        data = socket_client.recv(64).strip()
        if I2CD_WELCOME_BANNER_RE.match(data) is None:
            raise NameError('Invalid Welcome banner')

        # MSG
        data = ('READ' if read else 'SEND') + ' ' + hex(addr)
        for e in msg:
            data += ' ' + str(e)
        socket_client.send(data + '\n')
        data = socket_client.recv(64)
        data = I2CD_ANSWER_RE.match(data)
        if data is None:
            raise NameError('Invalid answer')
        if data.group('code') == '200':
            res = True
        elif data.group('code') == '201':
            data = I2CD_READED_STR_RE.match(data.group('str'))
            if data is None:
                raise NameError('Invalid answer')
            res = data.group('byte')
            res = int(res)

        # disconnect
        socket_client.send('QUIT\n')
        socket_client.close()

    except:
        pass

    return res

# -------------------------------------------------------------------------------------------------------------------- #

if __name__ == '__main__':

    # tcp client
    try:
        print remote_msg(addr=0x44, msg=[229, 1, 131, 1, 106])
        while True:
            r = remote_msg(addr=0x44, msg=[228, 17, 7, 152], read=True)
            print r
            time.sleep(1)
            if r == 0:
                pass
                # break
    except KeyboardInterrupt:
        sys.exit(0)

Le serveur (avec twisted) :

#!/usr/bin/python2.7 -u
__author__ = 'davixx'

# -------------------------------------------------------------------------------------------------------------------- #

from twisted.internet import protocol, reactor, endpoints
import time
import smbus
import sys

# -------------------------------------------------------------------------------------------------------------------- #

class I2CdProtocol(protocol.Protocol):

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def __init__(self, factory):
        self.factory = factory
        self.ready = False  # default status : the client is not ready to ask everything else than "Welcome"

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def connectionMade(self):
        self.factory.numProtocols += 1

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def connectionLost(self, reason):
        self.ready = False
        self.factory.numProtocols -= 1

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def ready_or_reject(self):

        # ready, this function return here
        if self.ready:
            return True

        # not ready... be verbose to the client, and lose connection
        self.transport.write('500 PLEASE WELCOME FIRST\n')
        self.transport.loseConnection()
        return False

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    @staticmethod
    def parse_addr_and_msg(data):
        try:
            # data split
            data = data.split(' ')

            # extract addr
            addr = data.pop(0).strip()
            assert addr.startswith('0x')
            addr = addr[2:]
            addr = int(addr, 16)
            assert 0 < addr < 128

            # extract message
            msg = list()
            for e in data:
                e = e.strip()
                e = int(e)
                assert 0 <= e <= 255
                msg.append(e)
            assert 0 < len(msg) < 64

        except:
            return None

        else:
            return I2cMessage(addr=addr, msg=msg)

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def dataReceived(self, data):

        # init
        data = data.strip()

        # must be ready, unless for welcome message
        if data != 'I2CD CLIENT':
            # should be ready
            if not self.ready_or_reject():
                return

        # Welcome message needed first
        if data == 'I2CD CLIENT':
            self.transport.write('200 WELCOME - YOU ARE ' + str(self.factory.numProtocols) + ' IN\n')
            self.ready = True

        # QUIT
        elif data == 'QUIT':
            self.ready = False
            self.transport.write('200 BYE BYE - REMAINING CLIENTS COUNT ' + str(self.factory.numProtocols - 1) + '\n')
            self.transport.loseConnection()

        # SEND 0x44 229 1 131 1 106
        elif data.startswith('SEND '):
            i2c_message = self.parse_addr_and_msg(data[5:])
            if i2c_message is None or not isinstance(i2c_message, I2cMessage):
                self.transport.write('500 INVALID COMMAND STRUCT\n')
            else:
                sended = i2c_message.send()
                self.transport.write(('200 SENDED' if sended else '400 FAILED') + '\n')

        # READ 0x44 228 17 7 152
        elif data.startswith('READ '):
            i2c_message = self.parse_addr_and_msg(data[5:])
            if i2c_message is None or not isinstance(i2c_message, I2cMessage):
                self.transport.write('500 INVALID COMMAND STRUCT\n')
            else:
                sended = i2c_message.send()
                if not sended:
                    self.transport.write('400 FAILED\n')
                res = i2c_message.read()
                self.transport.write('201 READED ' + str(res) + '\n')

        # unknown command...
        else:
            self.transport.write('500 UNKNOWN COMMAND\n')
            self.transport.loseConnection()

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

# -------------------------------------------------------------------------------------------------------------------- #

class I2CdFactory(protocol.Factory):

    def __init__(self):
        if hasattr(protocol.Factory, '__init__'):
            protocol.Factory.__init__(self)
        self.numProtocols = 0

    def buildProtocol(self, addr):
        return I2CdProtocol(factory=self)

# -------------------------------------------------------------------------------------------------------------------- #

class I2cMessage(object):

    def __init__(self, addr, msg):
        self.bus = smbus.SMBus(1)
        self.max_try = 3
        self.addr = addr
        self.msg = msg

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def log(self, msg, **kwargs):  # TODO
        print msg

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def _bus_write_byte(self, byte, max_try=3):

        # check type : int or hex
        if not isinstance(byte, int):
            raise NameError('byte must be an instance of int')

        # verbose
        line = ' > @' + str(hex(self.addr)) + ' : ' + str(byte).rjust(3, ' ') + ' (' + bin(byte).rjust(10, ' ') + ') '
        self.log(line, is_low_level=True)

        tried = 0

        while True:
            try:
                time.sleep(0.01)
                self.bus.write_byte(self.addr, byte)
                return
            except:
                if tried < max_try:
                    tried += 1
                    continue
                raise

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def _bus_read(self, max_try=3):

        # verbose
        line = ' < @' + str(hex(self.addr)) + 'READ BYTE'.ljust(16, ' ')

        tried = 0

        while True:
            try:
                time.sleep(0.01)
                r = self.bus.read_byte(self.addr)
                line += ' => ' + str(r)
                self.log(line, is_low_level=True)
                return r
            except:
                if tried < max_try:
                    tried += 1
                    continue
                line += ' => NULL'
                self.log(line, is_low_level=True)
                self.log('!!!! ' + 'self.bus.read_byte(addr)' + 'failed, tried:' + str(max_try), is_low_level=True)
                raise

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def send(self):

        tried = 0
        while True:
            try:
                for a_byte in self.msg:
                    self._bus_write_byte(a_byte)
                return True
            except Exception as err:
                if tried < self.max_try:
                    tried += 1
                    time.sleep(0.05)
                    continue
                self.log(' !! smbus failed to addr: ' + str(self.addr) + ' on dataset : ' + str(self.msg) + ' err: ' + str(err))
                return False

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def read(self):
        try:
            return self._bus_read()
        except:
            return None

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

    def __repr__(self):
        return str(hex(self.addr)) + ' => ' + str(self.msg)

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#

# -------------------------------------------------------------------------------------------------------------------- #

if __name__ == '__main__':

    # init twisted tcp server
    try:
        endpoints.serverFromString(reactor, 'tcp:422').listen(I2CdFactory())
        reactor.run()
    except KeyboardInterrupt:
        sys.exit(0)

# -------------------------------------------------------------------------------------------------------------------- #

gentoo atom regexps (ebuild name and versions)

Working on the Ansible Gentoo Portage emerge module, i’m testing some regexp for matching an atom string.

Atom is a Gentoo Portage pkgcore term.
An atom is a string representing a package name, and, optionally, it version range or version, the ebuild revision, the slot or slot and subslot.

To permit the future ansible module to handle the atom to be a parameter, i have to write some regexp for matching different atom alternative.

The first cases are simply to list, by parsing /usr/portage for avaiables packages, and atom : (here a simple bash/gawk/perl script to check the regexp)

#!/bin/bash

reg_ebuild_without_version='/^([a-z0-9\-]+)\/([a-zA-Z0-9\_\+\-]+)$/'
reg_ebuild_with_version='/^=?([a-z0-9\-]+)\/([a-zA-Z0-9\_\+\-]+)\-([^\-]+(\-r\d+)?)$/'

# this re
find /usr/portage/ -mindepth 2 -maxdepth 2 -type d | \
        gawk -F '/usr/portage/' '{ print $2; }' | \
        perl -pi -e "while(){ next if(${reg_ebuild_without_version}); print; }"

# this regexp must match a app-category/ebuild-name-X.Y.Z-rN
find /usr/portage/ -mindepth 3 -maxdepth 3 -type f -name '*.ebuild' | \
        perl -pi -e 's/\.ebuild$//g;' | \
        gawk -F '/usr/portage/' '{ print $2; }' | \
        gawk -F '/' '{ printf "%s/%s\n",$1,$3 }' | \
        perl -pi -e "while(){ next if(${reg_ebuild_with_version}); print; }"

Thoses regexp are not enougth to match any differents atom.

Some on the missing ones are already testing in the non-finished first version of the emerge module :

davixx/ansible/library/emerge (2013-01-20 version)

I hope, within 7 days, to finish the emerge_parse_atom function for handling successfully  almost 5-6 atom string cases.

memcached – List keys in PHP

According to :

There is no official way to list avaiables keys in a memcache process.

The second link show a way, within the debug interface to list them by hundred’s blocks. You can find a modest PHP implementation here :

function getMemcacheKeys($host = '127.0.0.1', $port = 11211){

        $mem = @fsockopen($host, $port);
        if($mem === FALSE) return -1;

        // retrieve distinct slab
        $r = @fwrite($mem, 'stats items' . chr(10));
        if($r === FALSE) return -2;

        $slab = array();
        while( ($l = @fgets($mem, 1024)) !== FALSE){
                // sortie ?
                $l = trim($l);
                if($l=='END') break;

                $m = array();
                // <STAT items:22:evicted_nonzero 0>
                $r = preg_match('/^STAT\sitems\:(\d+)\:/', $l, $m);
                if($r!=1) return -3;
                $a_slab = $m[1];

                if(!array_key_exists($a_slab, $slab)) $slab[$a_slab] = array();
        }

        // recuperer les items
        reset($slab);
        foreach($slab AS $a_slab_key => &$a_slab){
                $r = @fwrite($mem, 'stats cachedump ' . $a_slab_key . ' 100' . chr(10));
                if($r === FALSE) return -4;

                while( ($l = @fgets($mem, 1024)) !== FALSE){
                        // sortie ?
                        $l = trim($l);
                        if($l=='END') break;

                        $m = array();
                        // ITEM 42 [118 b; 1354717302 s]
                        $r = preg_match('/^ITEM\s([^\s]+)\s/', $l, $m);
                        if($r!=1) return -5;
                        $a_key = $m[1];

                        $a_slab[] = $a_key;
                }
        }

        // close
        @fclose($mem);
        unset($mem);

        // transform it;
        $keys = array();
        reset($slab);
        foreach($slab AS &$a_slab){
                reset($a_slab);
                foreach($a_slab AS &$a_key) $keys[] = $a_key;
        }
        unset($slab);

        return $keys;
}

mod_uploadchk : workspace et devspace – part 2

Bonsoir,

Le temps passe vite, The 2nd Law est sorti, et il faudrait peut-être que j’avance sur ce module avant de pouvoir me pencher sur une autre geekerie.

Pour commencer, j’ai voulu reprendre ce projet C en essayant de le développer dans Xcode. Je suis très satisfait de cet IDE pour Objective-C je me dis qu’il me sera peut-être aussi pratique que VIM pour du code C. Nous verrons bien… (nota ; créer un Workspace simplement au lieu de créer un project)

Je n’arrive pas à mettre la main sur “The Apache Modules Book”, Google sera donc mon ami ce soir. Commençons par remonter un apache de test, je viens de mettre à jour un vieux script d’install d’httpd : http://davixx.fr/pub/20100212_setup_httpd.sh.txt avec un téléchargement sur un miroir officiel plutôt que sur mon petit serveur : http://davixx.fr/pub/20121013_setup_httpd.sh.txt :

#!/bin/bash

httpd_ver="2.2.23"
httpd_dir="httpd-${httpd_ver}"
arch_url="http://apache.crihan.fr/dist/httpd/${httpd_dir}.tar.bz2"

# ca peux servir
function echec() {
	echo "fatal error [$1]";
	exit 127
}

# port a ecouter
proposed_httpd_port="3000"
echo -n "Sur quel port TCP faut-il binder httpd [${proposed_httpd_port}] ? "
read httpd_port
[ -z "${httpd_port}" ] && httpd_port="${proposed_httpd_port}"
if netstat -lpn 2>/dev/null | grep ":${httpd_port}"; then
	echo "le port <${httpd_port}> ne semble pas libre, vous etes sur (si oui : OK) ? "
	read confirm
	[ "${confirm}" != "OK" ] && echec "cancelled"
fi

# directory
cur_dir=`pwd`
proposed_httpd_fdir="${cur_dir}/${httpd_dir}-${httpd_port}"
echo -n "Dans quel repertoire mettre tout ca [${proposed_httpd_fdir}] ? "
read httpd_fdir
[ -z "${httpd_fdir}" ] && httpd_fdir="${proposed_httpd_fdir}"

# check
tfile="/tmp/${httpd_dir}"; [ -e "${tfile}" ] && echec "<${tfile}> exist"
tfile="${httpd_fdir}"; [ -e "${tfile}" ] && echec "<${tfile}> exist"

# go
cd /tmp || echec "cd /tmp"
[ -e "${httpd_dir}.tar.bz2" ] || wget "${arch_url}"
[ -e "${httpd_dir}.tar.bz2" ] || echec "download of <${arch_url}> failed"
tar xjvf ${httpd_dir}.tar.bz2 || echec "untar"
mv "${httpd_dir}" "${httpd_fdir}" || echec "mv"
cd "${httpd_fdir}" || echec "cd <${httpd_fdir}>"
./configure --prefix="${httpd_fdir}/apache2" --enable-so || echec "configure"
make || echec "make"
make install || echec "make install"
cd "${httpd_fdir}/apache2/conf" || echec "cd"
cat httpd.conf | sed 's/#ServerName new.host.name:80/ServerName localhost/' > _httpd_conf_1
mv -vf _httpd_conf_1 httpd.conf || echec "mv"
cat httpd.conf | sed "s/^Listen 80/Listen ${httpd_port}/" > _httpd_conf_1
mv -vf _httpd_conf_1 httpd.conf || echec "mv"
cd "${httpd_fdir}"
"${httpd_fdir}/apache2/bin/apachectl" configtest || echec "configtest"
echo
echo "c'est normalement fini"
echo "\"${httpd_fdir}/apache2/bin/apachectl\" start|stop"
echo

Je constate qu’il fonctionne toujours aussi bien pour http-2.2 ; au passage je constate que httpd-2.4 est disponibles, mais les distributions telle que Gentoo ne le mettent pas encore à disposition comme stable ; je vais donc me contenter de 2.2 pour le moment…

Je reviens dans un prochain post pour une alpha du module et du script de publication sur le httpd de tests ; en attendant jettez un oeil aux URL suivantes :

NSTableView, NSArrayController and Bindings : smart resizing columns

Après recherche, impossible de trouver le moyen facile de demander à un NSTableView de redimensionner intelligemment ses NSTableColumn de manière à ce que leur taille soit la même que si l’on double-cliquait directement sur la bordure à droite de la colonne.

Etant donné que les méthodes divergent en fonction des manières d’alimenter le NSTableView (via un dataSource, ou via les Cocoa Bindings sur les NSTableColumn), je précise qu’ici il s’agit de ce dernier cas, avec un NSArrayController.

Voici donc une Category pour NSTableView, il s’agit d’une V1, il lui manque notamment les points suivants :

  • Elargir proportionnellement les colonnes avec l’espace libre
  • Appeller si nécessaire setMinWidth et/ou setMaxWidth sur les colonnes

Mais cela vous apportera un point de départ ; le header :

//  NSTableView+autoColumnSizingForBindingWithNSArrayController.h
//  extramol
//  Created by David CHANIAL on 04/08/12.

#import <Cocoa/Cocoa.h>

@interface NSTableView (autoColumnSizingForBindingWithNSArrayController)
- (void)autoColumnSizingForBindingWithNSArrayController;
@end

Et pour l’implémentation :

//  NSTableView+autoColumnSizingForBindingWithNSArrayController.m
//  extramol
//  Created by David CHANIAL on 04/08/12.

#import "NSTableView+autoColumnSizingForBindingWithNSArrayController.h"

@implementation NSTableView (autoColumnSizingForBindingWithNSArrayController)

- (void)autoColumnSizingForBindingWithNSArrayController {
    NSArray *tableColumns = [self tableColumns];

    for (NSTableColumn *column in tableColumns){
        // init
        NSDictionary *bindingInformations = [column infoForBinding:@"value"];
        if(bindingInformations == nil) continue;
        id observedObject = [bindingInformations valueForKey:NSObservedObjectKey];
        if(observedObject == nil) continue;
        // Get Array
        NSArray *items = [observedObject valueForKeyPath:[bindingInformations valueForKey:NSObservedKeyPathKey]];
        if(items == nil) continue;
        // Get DataCell
        NSCell *columnDataCell = [column dataCell];
        NSMutableDictionary *att = [[NSMutableDictionary alloc] init];
        [att setValue:[columnDataCell font] forKey:NSFontAttributeName];
        CGFloat width = 5;
        // parse items
        for(NSString *v in items){
            CGFloat w = [[NSString stringWithFormat:@"%@", v] sizeWithAttributes:att].width;
            if(w>width) width = w;
        }
        // end
        width *= 1.1;
        width += [self intercellSpacing].width;
        // width += 1;
        // DCHLog(@"%@ width: %f", att, width);
        [att release];
        [column setWidth:width];
    }
}

@end
Nick Kew : "The Apache Modules Book" (ISBN 0-13-240967-4)

mod_uploadchk : Détecter coté serveur le filetype dès le début de l’upload – part 1

Dans le cas où l’on souhaite faire pouvoir uploader de gros fichier, sans avoir un client lourd en java ou flash, il n’y a pas – à ma connaissance – de moyen de faire échouer l’upload aussitôt que le filetype est détecté ; ceci permettrait pourtant de ne pas faire attendre à l’utilisateur la fin de la longue opération pour lui dire :

Le type de fichier que vous tentez d’uploader n’est pas celui attendu

C’est l’occasion pour moi de m’aventurer dans la création d’un nouveau module apache. Je ne sais pas si j’arriverais au (meilleur) résultat voulu, mais quoi qu’il en soit ce sera toujours une expérience d’acquise que j’ai plaisir à partager avec les lecteurs de ce post.

Nick Kew : "The Apache Modules Book" (ISBN 0-13-240967-4)Pour commencer, je me suis penché une bonne heure sur l’excellent livre de Nick Kew : “The Apache Modules Book” (ISBN 0-13-240967-4)

Il à été donc clair qu’il fallait coder un “(input) filter” et non un “handler”. Une fois les premiers octets reçus, il identifiera le “Magic Number” et agira en  fonction de la configuration du module.

J’avais déjà cherché il y a plusieurs mois si un module Apache existant pourrait satisfaire plus ou moins cette problématique, en vain,  mais je suis tout de même allé rejeter un oeil sur https://modules.apache.org/

Je n’ai trouvé aucun module satisfaisant mais, comme je suis assez fainéant, je suis content d’avoir trouvé un module qui pourrait me servir de base structurelle, et de modèle pour les opérations (hooks/filters, buckets/brigade) : mod_upload, développé – par coïncidence – par Nick Kew.

Pour cette première étape, je voulais poser les bases, nous avons donc la piste de travail, un module de référence, et je clôture sur le code du futur module dans sa version la plus light possible, ainsi que le Makefile associé :

#include <httpd.h>
#include <apr_strings.h>
#include <util_filter.h>
#include <http_config.h>
#include <http_log.h>

module AP_MODULE_DECLARE_DATA uploadchk_module ;

module AP_MODULE_DECLARE_DATA uploadchk_module = {
	STANDARD20_MODULE_STUFF,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL
} ;
all: mod_uploadchk install
mod_uploadchk:
        apxs2 -n uploadchk -c mod_uploadchk.c
install:
        apxs2 -n uploadchk -i -a mod_uploadchk.la
        /etc/init.d/apache2 restart
clean:
        rm -rf .libs
        rm -f mod_uploadchk.la mod_uploadchk.lo mod_uploadchk.o mod_uploadchk.slo

La suite dans un prochain post…