Django application development

Most developers work on their startups far away from the daylight – they keep it in complete secret till the end. I’ve decided to do something new – I have started a project which is available to preview during the whole development. It is available at urevs.com. I also share all the project info on dedicated blog: blog.urevs.com.

This project consists currently of authentication framework and a lot of backend solutions. Site allows users to login using OpenID’s, Google, Twitter, Facebook or Yahoo Accounts. Full details are available here – social bootstrap engine for Django.

I’d be very glad to hear from You, about every suggestion You have.

IPv4 CIDR to netmask in Python

I needed small function to validate IPv4 netmasks, havent found one, so I wrote my own. I’ve decided to use IPv4 CIDR notation to get corresponding netmasks. Here is the code:

def ipv4_cidr_to_netmask(bits):

    """ Convert CIDR bits to netmask """

    netmask = ''
    for i in range(4):
        if i:
            netmask += '.'
        if bits >= 8:
            netmask += '%d' % (2**8-1)
            bits -= 8
        else:
            netmask += '%d' % (256-2**(8-bits))
            bits = 0
    return netmask

Example usage is presented below. They also show how lambda mappings are useful in regular, daily use.

List all possible netmasks:

for netmask in map(lambda x: ipv4_cidr_to_netmask(x), range(0,33)):
    print netmask

0.0.0.0
128.0.0.0
192.0.0.0
224.0.0.0
240.0.0.0
248.0.0.0
252.0.0.0
254.0.0.0
255.0.0.0
255.128.0.0
255.192.0.0
255.224.0.0
255.240.0.0
255.248.0.0
255.252.0.0
255.254.0.0
255.255.0.0
255.255.128.0
255.255.192.0
255.255.224.0
255.255.240.0
255.255.248.0
255.255.252.0
255.255.254.0
255.255.255.0
255.255.255.128
255.255.255.192
255.255.255.224
255.255.255.240
255.255.255.248
255.255.255.252
255.255.255.254
255.255.255.255

Get netmask for /24:

print ipv4_cidr_to_netmask(24)

255.255.255.0

Is 255.255.254.0 valid IPv4 netmask?

print "255.255.254.0" in map(lambda x: ipv4_cidr_to_netmask(x), range(0,33))

True

Reverse search – get CIDR for 255.255.192.0 netmask

print map(lambda x: ipv4_cidr_to_netmask(x), range(0,33)).index('255.255.254.0')

23

As usual – enjoy :)

Simple WordPress plugin debugging

I’d like to present simplest way I know to debug WordPress plugins. It is a method I was using developing plugin to cloak URL‘s described earlier.

Below You can find the most minimalistic example plugin code possible – all it does is log REQUEST_URI to our debug file located at wp-content/plugins/debug. Row by row. Dirty and simple – and the most important – it does not require us to switch our WordPress to debug mode.

Plugin consists of three parts – plugin description header, debugging function and hook to main function which is executed right before page is rendered.

< ?php

/*
Plugin Name: test
Plugin URI:
Description:
Version:
Author:
Author URI:
*/

?>
< ?php

function debug($msg)
{
        $fp = fopen('wp-content/plugins/debug', 'a');
        fwrite($fp, $msg."\n");
        fclose($fp);
}

function main()
{
        debug($_SERVER['REQUEST_URI']);
}

add_action('init', 'main');

?>

Of course the most important part of the code is debug function which stores results we need to verify to our debug file.

jQuery-UI themeselect widget published

I’ve recently published jQuery-UI themeselect widget allowing users to add friendly looking, themable dropdown to instant theme switch.

In oppose to standard jQuery-UI themeswitcher, this one looks exacly like the rest of the theme.

My ui-themeselect has been published on New BSD License making it freely available and the code is hosted at Google Code. If You are familiar with Mercurial, you can clone the repository using command below:

hg clone https://ui-themeselect.googlecode.com/hg/ ui-themeselect

I have ripped the code out from my own Django / Google App Engine applications development Javascript bootstrap, because I think that someone else might consider it useful.

As an example how can it look inside jQuery-UI themed interface, take a look at this screenshot taken from one of my apps:

Enjoy! :)

Wygodny storage z użyciem memcache w Django

Czasami zachodzi konieczność napisania na szybko aplikacji w Django która standardowo coś tam trzyma w bazie danych, coś przetwarza i wyświetla. Jeśli aplikacja ma być prosta, a w bazie mają być trzymane proste struktury danych, to czy napewno musimy tyle czasu poświęcać na dopracowanie modeli? Jasne, że nie.

Poniższy kod oferuje nam możliwość trzymania dowolnych danych w postaci klucz-wartość w bazie danych. Dodatkowo – owe dane mogą mieć dowolną postać – może być to tekst, tablica asocjacyjna czy wartość typu Boolean.

Ponadto – biblioteczka ta opiera się o opisaną w poprzednim wpisie obsługę memcache’u w Django – co sprawia, że do póki nie zmienimy wartości danych, a ich obecność w cache’u nie zdąży wygasnąć – nie będziemy w ogóle obciążać naszej bazy danych. Osobiście wykorzystuję ten kod do trzymania ustawień aplikacji. Niektóre ustawienia są typu boolean, inne to stringi, jeszcze inne – tabele. Rozwiązanie sprawdza się świetnie.

Właściwy kod

Modele – models.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2009 nme.pl
# Dual licensed under MIT and GPL.

from django.db import models

class Vars (models.Model):

    key = models.CharField(max_length=50)
    value = models.TextField()

Proste prawda? Nie zapomnijcie wydać komendy django-admin syncdb która utworzy odpowiednią tabelę w bazie. Teraz czas na właściwą bibliotekę – vars.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2009 nme.pl
# Dual licensed under MIT and GPL.

""" Simple variables (key-value) handling with memcache """

import pickle,base64

import memcache,models

from settings import conf

class __Vars:

    def get (self,key,default=None):

        """ get Vars variable value """

        if memcache.enabled:
            value = memcache.get('va-%s' % key)
            if value != None:
                return pickle.loads(base64.b64decode(str(value)))
        settings = models.Vars.objects.filter(key=key)
        if settings:
            value = settings[0].value
            if value:
                return pickle.loads(base64.b64decode(str(value)))
            else:
                return default
        else:
            return default

    def __getitem__ (self,msg):

        return self.get(key)

    def set (self,key,value):

        """ set Vars variable value """

        value = base64.b64encode(pickle.dumps(value))
        if memcache.enabled:
            memcache.set('va-%s' % key,value)
        settings = models.Vars.objects.filter(key=key)
        if settings:
            setting = settings[0]
            setting.value = value
            setting.save()
        else:
            models.Vars(key=key,value=value).save()

    def has_key (self,key):

        """ return True or False if Vars variable exists """

        settings = models.Vars.objects.filter(key=key)
        if settings:
            return True
        else:
            return False

    def delete (self,key):

        """ deletes given key from Vars and memcache """

        if memcache.enabled:
            memcache.delete('va-%s' % key)
        settings = models.Vars.objects.filter(key=key)
        for i in settings:
            i.delete()

variables = __Vars()

Przykładowe użycie

Zastosowanie biblioteki z poziomu django-admin shell:

In [1]: from vars import variables

In [2]: if not variables.has_key('test'):
   ...:     variables.set('test','tekst')
   ...:
   ...:     

In [3]: print variables.get('test')
tekst

In [4]: variables.set('test',{'asd':1,'data':['struct','ure']})

In [5]: variables.get('test')['data'][1]
Out[5]: 'ure'

In [6]: variables.delete('test')

In [7]: variables.get('test')

In [8]: variables.has_key('test')
Out[8]: False

Jak to działa?

Składowanie dowolnego typu danych w rekordzie bazy zrealizowane jest w oparciu o dwie standardowe biblioteki pythonowe – picklebase64. Pierwsza z nich oferuje zakodowanie dowolnej struktury danych do stringa, druga natomiast – zastosowanie notacji base64 – przez co zabezpieczamy się przed ewentualnymi problemami z obsługą znaków z którymi nasz bazodanowy backend mógłby mieć ewentualnie problemy. Innymi słowy – rozwiązanie przedstawione powyżej gwarantuje nam pełną przenośność danych.

Zachęcam gorąco do korzystania :)

Memcache w Django: krok ku lepszej skalowalności

Podczas tworzenia aplikacji webowych, warto w miarę wcześnie pomyśleć o skali z jaką nasze rozwiązanie będzie miało się w przyszłości zmierzyć. Niezależnie od tego czy owa aplikacja ma pracować w jednym przedsiębiorstwie, czy próbuje zainstnieć szerzej, w sieci – w każdym przypadku może się okazać, że odniesie sukces. To z kolei sprawi, że ilość jej użytkowników urośnie… Firma może się rozwinąć, przejąć konkurencję, aplikacja webowa może się okazać takim strzałem w dziesiątkę jak nie tak dawno temu Nasza-Klasa – nigdy nie wiadomo :) W momencie gdy miałaby go odnieść, nie będzie już zbyt wiele czasu na przebudowę kodu, a napewno nie będzie go na jego całkowitą reorganizację.

Jeśli rozwiązanie jest skryptem uruchamianym od święta – może to być zwykły skrypt CGI. Jeśli jednak miałby być uruchamiany częściej – wartałoby już pomyśleć o mod_php czy mod_python… Albo jeszcze lepiej – fastcgi, a w przypadku pythona – wsgi. Kiedy użycie aplikacji rośnie nadal – frontend mnożymy na kolejne węzły stawiając przed nimi balancera robiącego za reverse proxy. A w backendzie klastrujemy… ale co na styku macierz, klaster? Ilość zapytań do bazy rośnie, io waity zaczynają rosnąć i robi się nieciekawie… Da się to rozwiązać?

Jak to robi Google

Google od jakiegoś czasu pokazuje nam jak powinny być tworzone intensywnie wykorzystywane aplikacje webowe i na jak zorganizowanym backendzie powinny pracować, aby skalowały się najefektywniej. Myślę, że warto z tej wiedzy skorzystać zanim io waity zaczną spędzać nam sen z powiek.

Miałem okazję napisać sobie kilka małych aplikacji które pracują w chmurze na platformie Google – tzw. Google App Engine (w skrócie GAE). Warto było poświęcić trochę czasu aby to rozwiązanie poznać. Łatwiej było mi dzięki temu można zrozumieć między innymi dlaczego poszczególne serwery Google nie są potężnymi serwerami rackowymi z bardzo silnymi procesorami, ale raczej „lżejszymi” serwerkami, ale za to wypełnionymi po brzegi koścmi pamięci.

Dlaczego takie lżejsze maszyny? Poza oszczędnością energii i pieniędzy są one wystarczające do obsługi pythonowych frameworków, w przeciwieństwie do tych PHP’owych.

Google skupia się na Javie i Pythonie, gdzie prawie pewne jest to, że ich rozproszony Datastore – system bazodanowy, został stworzony w oparciu o język Python. Język ten znany jest m.in z wydajności, szczególnie, jeśli osoba która go używa stosuje się do najbardziej podstawowych zasad jak nie allokowanie ogromnej ilości pamięci itp. Dlaczego więc w serwerach Google tyle pamięci? Odpowiedzią jest memcache.

Google chwali się tym, że stworzyli sobie własną implementację cache, inspirowaną oprogramowaniem memcached i zachęca, aby developerzy aplikacji App Engine’owych również korzystali z memcache’a.

Na czym polega memcache

Uproszczony graf przedstawiający działanie memcache’u:

Koncepcja działania jest prosta – zamiast odwoływać się bezpośrednio do bazy danych, odwołujemy się do memcache’a – jeśli trafiliśmy na dane (hit), wykorzystujemy je, jeśli nie (miss), dopiero wtedy odwołujemy się do bazy, jednocześnie zapisując pobrane z bazy dane w naszym cache’u. Dzięki temu prostemu zabiegowi, nasza strona, która podczas każdego odwołania do niej potrzebuje jakichś danych z bazy, jest w stanie funkcjonować praktycznie w ogóle jej o nic nie odpytując – wszystko co potrzebne będzie trzymać w pamięci podręcznej.

Zastosowanie i dokumentacja

Aby wykorzystać memcache w Google App Engine, polecam zapoznanie się z dokumentacją na stronach Google.

A co jeśli chcielibyśmy wykorzystać memcache w Django? Naturalnie – nic nie stoi na przeszkodzie – Django posiada coś takiego jak cache framework, który na dodatek jest bardzo dobrze udokumentowany.

Django samo z siebie jest przystosowane do wykorzystania cache’u przy obsłudze widoków czy template’ów, ale również oferuje dostęp niskopoziomowy – niemalże analogiczny do tego z Google App Engine… Niemalże, bo jakoś wersja Google bardziej przypadła mi do gustu. Dlatego przygotowałem sobie prosty interfejs, który wydaje mi się być odrobinę bardziej logiczny. Kod oczywiście załączam. Mam nadzieję, że komuś się przyda.

Konfiguracja systemu, projektu Django oraz moja niskopoziomowa obsługa

Zakładając, że pracujemy na systemie GNU Debian/Ubuntu oraz Django jest już w systemie jakąś drogą zainstalowane (osobiście w tym akurat przypadku polecam instalację z oryginalnych źródeł nad paczką Django z dystrybucji), doinstalowujemy obługę memcached, włączamy ją i restartujemy demona memcached:

sudo -i
aptitude install memcached python-memcache
sed -i 's/no/yes/' /etc/default/memcached
service memcached restart
exit

teraz w naszym projekcie Django włączamy obsługę cache:

w pliku settings.py:

CACHE_BACKEND = 'memcached://127.0.0.1:11211/'

jeśli chcemy wykorzystać moją małą biblioteczkę poniżej, do settings.py musimy dodać też konfigurację memcache:

class conf:
    class memcache:
        disabled = False
        timeout = 600

Moja niskopoziomowa obsługa memcache.py:

#!/usr/bin/python
# -*- coding: utf-8 -*-

""" cache low-level backend based on memcached """

from django.core.cache import cache

from settings import conf

def get(key, default=None, timeout=conf.memcache.timeout):

    """ returns value for given key from cache, and refreshes it
    in memcache with automatic conversion from unicode
    to str - fix to:
    __import__() argument 1 must be string without null bytes, not str """

    value = cache.get(key, default)

    if value is not default:
        cache.set(key, value, timeout)

    if type(value) is unicode:
        value = str(value)

    return value

def set(key, value, timeout=conf.memcache.timeout):

    """ sets a value for given key in cache """

    cache.set(key, value, timeout)

def delete(key):

    """ remove given key from cache """

    cache.delete(key)

# in the name of syntatic sugar ;) 

disabled = bool(conf.memcache.disabled)
enabled = not disabled

A przykład zastosowania pojawi się jutro albo pojutrze – w nowym wpisie.

Prosty plugin jQuery: Dynamiczny favicon

Ten prosty wpis ma za zadanie przybliżyć Wam dwa tematy jednocześnie – jak stworzyć prosty plugin do jQuery oraz jak z poziomu JavaScriptu podmienić obrazek favicon dla strony. Zamierzam przedstawić plugin który właśnie taką czynność wykonuje :)

jQuery chyba nie trzeba przedstawiać. Jest to w mojej opinii najłatwiejsza w użyciu biblioteka czyniąca język JavaScript prostym i wygodnym w użyciu. Co więcej – można ją bardzo łatwo rozszerzać o dodatkową funkcjonalność za pomocą pluginów. Uważam, że warto wiedzieć jak taki plugin można sobie szybko przygotować.

jquery.favicon_replace.js wygląda następująco:

(function($) {
    $.fn.favicon_replace = function(url) {
        $('head link').each(function() {
            if ($(this).attr('rel') == 'shortcut icon') {
                $(this).remove();
            }
        });
        $('<link rel="shortcut icon" href="'+url+'" />')
            .appendTo('head');
    };
})(jQuery);

Jak powyższy kod działa: tworzymy closure której jako parametr podajemy strukturę jQuery. Wewnątrz closury definiujemy funkcję, która sprawdza atrybut rel każdego taga typu link wewnątrz head porównując go do shortcut icon. Znaleziony w ten sposób element jest kasowany. Następnie tworzony jest nowy link typu shortcut icon, z nowym url’em. Urla podajemy funkcji jako parametr. Closura w postaci anonimowej funkcji jest wykonywana, a w efekcie jQuery zyskuje nową funkcję.

Zastosowanie tego plugina jest proste – oto przykład:

$(document).favicon_replace('/images/new-favicon.ico');

Warto napomnieć, że favicon wcale nie musi być ikoną. Możecie równie dobrze zaserwować przeglądarce animowanego gifa, nawet z rozszerzeniem ico, który zostanie automatycznie przeskalowany przez przeglądarkę i animowany na pasku adresu :)

Python – Demonologia

artwork by ~laurD on DeviantART

Python jest świetnym językiem do całej palety zastosowań. Służy on pomocą zarówno administratorom systemów, osobom które potrzebują napisać prosty parser, a nawet twórcom aplikacji webowych – dzięki takim frameworkom jak Django. W poniższym tekscie chciałbym pokazać jak łatwo jest stworzyć usługę systemową (ang. daemon) w języku Python, która będzie wykonywała dla nas określoną czynność – jako przykład przedstawię fakedns – bardzo prostego daemona DNS, który na dowolne zapytanie o adres IP będzie zwracał zawsze ten sam.

Dlaczego Python? Co z innymi językami?

Nie lepiej mieć aplikację którą można skompilować? Absolwenci kierunków informatycznych jeszcze kilka lat temu dowiadywali się na studiach, że najbardziej zaawansowanym językiem do wszelakich zastosowań jest Java. Mieli praktyczne doświadczenie z tworzenia aplikacji w Javie… i zaczęli pisać. Kod który chciałbym tu przedstawić pisałem dwa lata temu. Zanim się za niego zabrałem, zacząłem od szukania już gotowego rozwiązania za pomocą Google. Znalazłem, nawet kilka. Jedno w Javie. O zgrozo. Ja wiem, że się da. Wiem, że Java jest naprawde zaawansowanym językiem. Gotowa aplikacja wymaga jednak dużego nadkładu. Trzeba przygotować środowisko, przygotować konkretną ilość pamięci… i czekać kiedy aplikacja się wyłoży. Takie mam przynajmniej doświadczenia z aplikacjami w Javie, które pisali programiści zaraz po studiach, pracując w profesjonalnej firmie software’owej.

A inne języki? Natualnie. Jest C. Świetny język, ale czy mamy wystarczająco dużo czasu aby w nim pisać nawet małe aplikacje? .NET ? Wolne żarty. Nie dość, że pod Linuxem jest bardzo grubymi nićmi przyszywany, to i nie mamy pewności, czy Microsoft nie powie pewnego dnia hola, jedynie na ich, komercyjnej platformie. Naddatek potrzebnej mocy obliczeniowej, pamięci, wymaganej przestrzeni dyskowej… i ta świadomość, że nie jest to dopracowane i pewnie nigdy nie będzie – niczym Samba próbująca naśladować również kalekie i dziurawe, Microsoftowe domeny.

A co ze skryptowymi? Mnie na myśl przychodzą trzy alternatywy – Lua, Ruby i skrypt w bashu.

Lua – niesamowicie przejrzysty język. Piekielnie szybki, realizowany przez wręcz mikroskopijną bibliotekę. Na jego niekorzyść trzeba jednak zaliczyć wręcz niemal konieczność liczenia się z faktem, że jeśli rozwiązanie które potrzebujemy stworzyć, będzie wymagało biblioteki – będziemy zmuszeni długo jej szukać lub przygotować samemu jej obsługę za pomocą języka C.

Ruby – chyba najbliższy Pythonowi. Dlaczego nie. Bibliotek jest coraz więcej, jest wydajny. Miałem okazję jakiś czas temu nawet coś napisać w tym języku (a dokładnie – przepisać mały framework z Pythona), ale… jakoś w Pythonie pisze mi się lepiej. Co ważne – Ruby jest głęboko zakorzeniony w Perlu. Programistom mówi się jak powinni pisać kod, jednak zawsze mogą robić w to w stylu Perlowym – pisać szybko i wydajnie, jednak przeczytanie i przeanalizowanie go po jakimś czasie staje się piekłem. Kod w Pythonie jest de facto zawsze taki sam, nie zależnie kto by go pisał – czytanie go jest proste i przyjemne.

No i jeszcze uzupełnie o język skryptowy – Bash. Można w nim naprawdę dużo zrobić. Warto jednak zastanowić się na ile aplikacja będzie zaawansowana i czy zależy nam na wydajności. Podam przykład. Stworzyłem kiedyś skrypt w bashu generujący w oparciu o kilka plików wejściowych reguły dla firewalla, reguły shapera oraz treść dhcpd.conf. Skrypt wewnątrz wywoływał po kilka tysięcy razy awk/sed’a itp. Na maszynie jednoprocesorowej (Pentium 4 1.4 GHz), która pełniła funkcję routera dla sieci 100 Mbit/s, przy mniejszym obciążeniu – skrypt wykonywał się kilka minut. Przy większym – godzinę. Za każdym razem uruchamiając awk, spawnował odrębnego basha. Cóż. Przepisałem rozwiązanie na język Lua. Skrypt niezależnie od obciążenia maszyny wykonywał się w 0.02 sekundy. Jeśli byłby to Python czy Ruby – efekt byłby zapewne zbliżony do Lua.

Tworzenie usługi systemowej dla Linux/Unix

OK, zaczynamy. Usługa systemowa powinna przede wszystkim:
- wiedzieć jak pisać do sysloga
- zrzucić uprawnienia root’a
- odkleić się od stdin i stdout i przejść w tło
Jeśli taka usługa dodatkowo potrzebuje obsługiwać jakiś systemowy port TCP/UDP (o numerze niższym niż 1024) – zanim zrzuci ona prawa roota, powinna otworzyć sobie określonego socketa.

Nie będę tłumaczył poszczególnych fragmentu kodu ponieważ uważam, że jest on wystarczająco przejżysty.

fakedns.py

#!/usr/bin/python -u
# -*- coding: utf-8 -*-
" Fake DNS server: Always answer with the same, defined as conf.destip reply, listen at conf.listen "

class conf:
    listen='ADRES_IP_NA_KTÓRYM_APLIKACJA_MA_NASŁUCHIWAĆ'
    destip='ADRES_IP_NA_KTÓRY_APLIKACJA_BĘDZIE_KIEROWAĆ'

import os,sys,pwd,grp
import logging,logging.handlers
import socket

log=logging.getLogger('fakedns')
logging.basicConfig()
logging.root.setLevel(level=logging.INFO)
#logging.root.setLevel(level=logging.DEBUG)
hdlr=logging.handlers.SysLogHandler('/dev/log')
log.addHandler(hdlr)

def drop_privileges(uid_name='nobody',gid_name='nogroup'):
    starting_uid=os.getuid()
    starting_gid=os.getgid()
    starting_uid_name=pwd.getpwuid(starting_uid)[0]
    log.info('fakedns: drop_privileges: started as %s/%s'%(pwd.getpwuid(starting_uid)[0],grp.getgrgid(starting_gid)[0]))
    if os.getuid()!=0:
        log.info("fakedns: drop_privileges: already running as '%s'"%starting_uid_name)
        return
    if starting_uid==0:
        # Get the uid/gid from the name
        running_uid = pwd.getpwnam(uid_name)[2]
        running_gid = grp.getgrnam(gid_name)[2]
        # Try setting the new uid/gid
        try:
            os.setgid(running_gid)
        except OSError,e:
            log.error('Could not set effective group id: %s'%e)
        try:
            os.setuid(running_uid)
        except OSError,e:
            log.error('Could not set effective user id: %s'%e)
        # Ensure a very convervative umask
        new_umask=077
        old_umask=os.umask(new_umask)
        log.info('fakedns: drop_privileges: Old umask: %s, new umask: %s'%(oct(old_umask),oct(new_umask)))
    final_uid=os.getuid()
    final_gid=os.getgid()
    log.info('fakedns: drop_privileges: running as %s/%s'%(pwd.getpwuid(final_uid)[0],grp.getgrgid(final_gid)[0]))

def become_daemon(home='.',stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
    log.info('fakedns: deamonize')
    # perform first fork
    try:
        if os.fork()>0:
            sys.exit(0)
    except OSError,e:
        log.info("fakedns: 1st fork failed: %s"%e.strerror)
        sys.exit(1)
    os.setsid()
    os.chdir(home)
    os.umask(0)
    # second fork
    try:
        if os.fork()>0:
            sys.exit(0)
    except OSError,e:
        log.info("fakedns: 2nd fork failed: %s"%e.strerror)
        sys.exit(1)
    i=open(stdin,'r')
    o=open(stdout,'a+')
    e=open(stderr,'a+',0)
    os.dup2(i.fileno(),sys.stdin.fileno())
    os.dup2(o.fileno(),sys.stdout.fileno())
    os.dup2(e.fileno(),sys.stderr.fileno())

class DNSQuery:
    def __init__(self, data):
        self.data=data
        self.dominio=''

        tipo = (ord(data[2]) >> 3) & 15     # Opcode bits
        if tipo == 0: # Standard query
            ini=12
            lon=ord(data[ini])
            while lon != 0:
                self.dominio+=data[ini+1:ini+lon+1]+'.'
                ini+=lon+1
                lon=ord(data[ini])

    def respuesta(self, ip):
        packet=''
        if self.dominio:
            packet+=self.data[:2] + "\x81\x80"
            # Questions and Answers Counts
            packet+=self.data[4:6] + self.data[4:6] + '\x00\x00\x00\x00'
            # Original Domain Name Question
            packet+=self.data[12:]
            # Pointer to domain name
            packet+='\xc0\x0c'
            # Response type, ttl and resource data length -> 4 bytes
            packet+='\x00\x01\x00\x01\x00\x00\x00\x3c\x00\x04'
            # 4bytes of IP
            packet+=str.join('',map(lambda x: chr(int(x)), ip.split('.')))
        return packet

if __name__ == '__main__':
    udps = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        udps.bind((conf.listen,53))
    except socket.error, (val,e):
        log.info("fakedns: %s"%e)
        sys.exit(1)
    log.info('fakedns: dom.query. 60 IN A %s' % conf.destip)
    drop_privileges()
    become_daemon()
    try:
        while 1:
            data, addr = udps.recvfrom(1024)
            #log.info(addr)
            p=DNSQuery(data)
            udps.sendto(p.respuesta(conf.destip), addr)
            #log.info('%s -> %s' % (p.dominio, conf.destip))
            log.info('fakedns: %s -> %s'%(p.dominio,addr[0]))
    except KeyboardInterrupt:
        log.info('fakedns: done.' % (p.dominio, conf.destip))
        udps.close()
    except:
        log.info('fakedns: crashed serving %s (%s)'%(addr, data))

Instalacja Mercurial z GUI TortoiseHG pod Ubuntu 9.04

mercurial-3Mercurial jest jednym z najpopularniejszych ostatnio systemów kontroli wersji. Został on napisany w języku Python i coraz więcej projektów jest do niego migrowanych. Google Code umożliwia wybór jednego z dwóch systemów kontroli wersji – jednym z nich jest SVN, drugim właśnie Mercurial.

Różnice między scentralizowanym a rozproszonym systemem kontroli wersji

Zasada działania Mercuriala jest zbliżona do Git‘a – oba są tzw. rozproszonymi systemami kontroli wersji. SVN z drugiej strony jest systemem zcentralizowanym. Praca na systemie rozproszonym jest o tyle wygodna, że każda lokalna kopia repozytorium może być traktowana jako pełnoprawna i całkowicie niezależna. Rozwijając kod który jest obsługiwany w takim systemie kontroli wersji, commitowanie, czyli zatwierdzanie mniejszych porcji zmian jest znacznie wygodniejsze i nie wymaga zaprzątania głowy głównego repozytorium… którego tak naprawdę wcale nie musi być.

Jak to robi Linus Torvalds?

Większe projekty, takie jak jądro Linuxa coraz częściej przechodzą na rozproszone systemy kontroli wersji. Linus Torvalds bardzo zachęca wszystkich do migrowania do Gita z SVN’a. Git został napisany właśnie z myślą o developerach jądra systemu Linux. Głównymi zaletami według niego są dwie kwestie – pierwsza: znacznie wygodniejsze merge’owanie (łączenie zmian wprowadzonych przez różnych developerów) oraz druga – rozproszone repozytoria pozwalają na hierarchiczne scalanie kodu (z punktu widzenia pracy grupowej). Dzięki tej drugiej, Linus może powierzyć np. opiekę nad modułami jądra dotyczącymi sieci komuś, kto zna się na tym konkretnym temacie lepiej, potrafi weryfikować zmiany i poprawki. Zebrane do repozytorium przez opiekuna drzewa sieci poprawki, Linus może włączyć do jego repozytorium, które traktowane jest jako główne mając pewność, że wszystkie zmiany zostały odpowiednio zweryfikowane.

Kontrola wersji z linii komend

Mercuriala można zainstalować ze standardowego pakietu w Ubuntu. Na pakiet składa się jedna aplikacja – hg. Za jej pomocą można wykonać wszystkie operacje na repozytorium z poziomu linii komend.

$ hg
Mercurial Distributed SCM

basic commands:

 add        add the specified files on the next commit
 annotate   show changeset information by line for each file
 clone      make a copy of an existing repository
 commit     commit the specified files or all outstanding changes
 diff       diff repository (or selected files)
 export     dump the header and diffs for one or more changesets
 forget     forget the specified files on the next commit
 init       create a new repository in the given directory
 log        show revision history of entire repository or files
 merge      merge working directory with another revision
 parents    show the parents of the working directory or revision
 pull       pull changes from the specified source
 push       push changes to the specified destination
 remove     remove the specified files on the next commit
 serve      export the repository via HTTP
 status     show changed files in the working directory
 update     update working directory
 view       start interactive history viewer

use "hg help" for the full list of commands or "hg -v" for details

Kontrola wersji z poziomu interfejsu graficznego

Istnieje jednak możliwość pracy graficznej. Windowsowi użytkownicy jako graficzny interfejs zwykle wykorzystują TortoiseHG, który wywodzi się z TortoiseSVN. Pod Linuxem możemy również wykorzystać TortoiseHG.

Instalacja Mercuriala i TortoiseHG pod Ubuntu 9.04

Uruchomienie komend zamieszczonych poniżej sprawi, że nasz Mercurial zostanie „podniesiony” do najnowszej stabilnej wersji, pojawi się nam TortoiseHG również w wersji stabilnej, a następnie TortoiseHG zostanie włączone do domyślnego menadżera plików w Gnome – Nautilusa. Zainstalowana zostanie również aplikacja Meld, która czyni merge’owanie całkiem przyjemną czynnością w interfejsie graficznym :)

# Najpierw przechodzimy na roota, tego którego w Ubuntu nie ma ;)  

sudo -i

# Dodajemy drzewo stable repozytorium Mercuriala z Ubuntu Launchpad PPA

cat >/etc/apt/sources.list.d/mercurial.list << EOF
deb http://ppa.launchpad.net/mercurial-ppa/stable-snapshots/ubuntu jaunty main
deb-src http://ppa.launchpad.net/mercurial-ppa/stable-snapshots/ubuntu jaunty main
EOF
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-key 323293EE

# Dodajemy drzewo stable repozytorium TortoiseHG z Ubuntu Launchpad PPA
cat >/etc/apt/sources.list.d/tortoisehg.list << EOF
deb http://ppa.launchpad.net/tortoisehg-ppa/stable-snapshots/ubuntu jaunty main
deb-src http://ppa.launchpad.net/tortoisehg-ppa/stable-snapshots/ubuntu jaunty main
EOF
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-key D5056DDE

# odświeżamy listę repozytoriów i pobieramy pakiety oraz dodajemy obsługę bindingsów Pythona w Nautilusie

aptitude update
aptitude install -y python-nautilus mercurial tortoisehg tortoisehg-nautilus meld

# doinstalowujemy pakiet iniparse
wget http://iniparse.googlecode.com/files/python-iniparse_0.3.1-1_all.deb
dpkg -i python-iniparse_0.3.1-1_all.deb
rm python-iniparse_0.3.1-1_all.deb

Po wykonaniu tych komend najlepiej się przelogować ponownie, aby Nautilus zdał sobię sprawę z wprowadzonych zmian. Można też (już na swoim użytkowniku) uruchomić komendę:

nautilus -q

Która spowoduje przeładowanie Nautilusa (może nam zniknąć do następnego zalogowania zawartość pulpitu). Jeśli wszystko pójdzie pomyślnie, w efekcie możemy zarządzać repozytorium w następujący sposób:

mercurial-1

Powiadomienia SMS mBank mobile – drugie podejście

Dwa miesiące temu przedstawiłem dwa sposoby w jaki można sobie uruchomić powiadomienia SMS od aplikacji lub o przychodzących mailach. Oba miały pewne wady więc zdecydowałem się powrócić do tematu raz jeszcze.

Pierwszym rozwiązaniem, było wykorzystanie +48xxxxxxxxx@text.plusgsm.pl
Rozwiązanie dobre, bo dostajemy natychmiast powiadomienie na komórkę. Dodatkowo większość skrzynek pocztowych ma możliwość ustawienia automatycznej kopii na drugi adres – więc w dosłownie chwilę możemy sobie ustawić automatyczne powiadomienie o mailach.
Rozwiązanie to ma jednak swoją wadę – dostajemy pełną treść, która nie zmieści się w jednym SMS’ie jeśli w mailu jest jakakolwiek treść poza samym tematem.

Drugim zasugerowanym przeze mnie rozwiązaniem było wykorzystanie możliwości wysyłania powiadomień SMS z Google Calendar. To rozwiązanie również jest dobre. Możemy wykorzystać konto w Google Apps, dzięki czemu adres powiadomień jest bardziej uniwersalny, ale ma też swoje wady – chodzi mianowicie o dzienny limit powiadomień SMS z kalendarza Google. Jak się przekonałem, taki limit jest. Dokładnej liczby nie znam, ale w przybliżeniu to około 25 SMS’ów.

Rozwiązanie optymalne?

Postanowiłem zatem poszukać czegoś po środku. Wykorzystałem obsługę skrzynki Google do pobierania wiadomości, a następnie jedynie nadawcę i temat przekazuje do usługi text.plusgsm.pl. Wydaje mi się, że rozwiązanie jest optymalne. Oto kod:

#!/usr/bin/env python
# -- encoding: utf8 --

__author__ = 'mw AT nme.pl'
__version__ = '1.4'
__date__ = 'pią, 25 wrz 2009, 13:22:28 CEST'

class setup:
    verbose = False
    class google:
        login = 'KONTO@GOOGLE'
        password = 'HASŁO'
    class notify:
        recipient="+48xxxxxxxxx@text.plusgsm.pl"

import poplib
import email
import email.Header
#import sms
import smtplib
import re

class Fwd:

    def __init__(self,address,subject):

        mesg = "From: %s\nTo: %s\nSubject: %s\n\n" % (address, setup.notify.recipient, subject)
        server = smtplib.SMTP('smsb.plusgsm.pl')
        if setup.verbose:
            server.set_debuglevel(1)
        server.sendmail (address, setup.notify.recipient, mesg)
        server.quit()

class Mailbox:

    def __init__(self):

        self.server = poplib.POP3_SSL('pop.gmail.com', 995)
        self.server.user(setup.google.login)
        self.server.pass_(setup.google.password)
        self.server.set_debuglevel(0)

        # fetch
        count = self.server.stat()[0]
        if setup.verbose:
                print count
        if count:
            for i in range(1,count+1):
                (header, msg, octets) = self.server.retr (i)
                mail = email.message_from_string('\n'.join(msg))
                address = email.Header.decode_header(mail['From'])[1]
                if address[1]:
                    address = address[0].decode(address[1],'ignore')
                else:
                    address = address[0]
                address = re.sub('>\s*$','',re.sub('^\s*< ','',address))
                subject = email.Header.decode_header(mail['Subject'])[0]
                if subject[1]:
                    subject = subject[0].decode(subject[1],'ignore')
                else:
                    subject = subject[0]
                #print subject
                #sms.parse(subject.split())
                Fwd(address,subject)
                #self.server.dele (i)

        # commit
        self.server.quit()

mbox = Mailbox()

Aby te powiadomienia działały, na koncie shellowym umieszczamy skrypt, nadajemy mu prawa do uruchomienia, przechodzimy do edycji crona:

chmod +x pop2smsV2.py
crontab -e

dodajemy zadanie do crona, aby był uruchamiany co minutę:

* * * * * /home/NAZWA/bin/pop2smsV2.py >/dev/null 2>&1

Podsumowanie

Teraz to rozwiązanie najbardziej przypomina mi to które miałem w Erze. Trzeba niestety użyć do tego shella z cronem. OVH odpada z dwóch powodów – brak crona oraz brak dostępu do sieci z poziomu shella. Może i by się dało uruchomić to jako CGI z ich webowym cronem, ale niestety mija się to trochę z celem, ponieważ cron OVH może być uruchamiany najczęściej co godzinę.

Przewagą tego rozwiązania nad powiadomieniem z Ery jest brak limitu skrzynki 12 MB. Limitem jest ponad 7 GB co nie stanowi już problemu. Jeśli nie chcemy zostawiać na skrzynce śladu – możemy usunąć komentarz z linii self.server.dele (i) – co spowoduje kasowanie przesłanych jako SMS wiadomości.

Zaletą obsługi powiadomień za pomocą crona jest to, że możemy ustalić sztywne godziny w których skrypt ma działać – możemy ustawić, że powiadomienia mogą przychodzić pomiędzy 8:00, a 16:00 oraz 18:00 – 22:00 (trzeba się wtedy liczyć z faktem, że o ósmej rano otrzymamy wszystkie SMS’y z nocy). Jeśli chcemy mieć powiadomienia aktywne w godzinach 8:30 – 22:30, robimy to następującymi regułami:

30-59   8 * * * /home/nme/sms-notify/pop2smsV2.py >/dev/null 2>&1
*    9-21 * * * /home/nme/sms-notify/pop2smsV2.py >/dev/null 2>&1
0-30   22 * * * /home/nme/sms-notify/pop2smsV2.py >/dev/null 2>&1

Dodatkowo – dzięki temu rozwiązaniu mamy adres do powiadomień, znacznie łatwiejszy do zapamiętania – bo w postaci np. powiadomienia@nasza-domena.pl :)

Następnya strona »