Wprowadzenie do systemu kontroli wersji Mercurial

mercurialPoniższy tekst jest jedynie wprowadzeniem do pracy z Mercurialem. Osoby które chcą pogłębić swoją wiedzę w zakresie tego tematu – zachęcam do przeczytania darmowej książki dostępnej w Internecie – Mercurial: The Definitive Guide.

Aby pracować na Mercurialu musimy go najpierw zainstalować. Dla systemu Windows Mercurial jest jedną wygodną do zainstalowania paczką. Informację jak zalecam to zrobić pod Ubuntu, zamieściłem tu: Instalacja Mercurial z GUI TortoiseHG pod Ubuntu 9.04.

Konfiguracja wstępna

Zanim rozpoczniemy pracę z Mercurialem, należy w swoim katalogu domowym utworzyć plik .hgrc w którym konfigurujemy swojego użytkownika. Plik ten powinien zawierać następującą treść:

[ui]
username = Imie Nazwisko < konto@domena.pl >

Pod Linuxem lub Unixem warto również ustawić zmienną środowiskową EDITOR wskazującą na nasz
ulubiony edytor. W moim wypadku, w pliku .bashrc w katalogu domowym
musiałem dodać na końcu linijkę:

export EDITOR="vim"

W tym momencie możemy już przystąpić do pracy.

Tworzenie repozytorium

Załóżmy, że jesteśmy w katalogu testy.

Utwórzmy w nim plik notatki.txt zawierający jedną linijkę tekstu:

To są moje notatki

Utwórzmy też katalog dokumenty, a w nim umieśćmy plik do-zrobienia.txt zawierający następującą treść:

[ ] Zrobić przelewy za październik
[ ] Podać do Energetyki stan licznika

Struktura plików i katalogów wygląda zatem następująco:

user@host testy $ ls
dokumenty  notatki.txt
user@host testy $ ls dokumenty/
do-zrobienia.txt

Możemy w tym momencie utworzyć sobie repozytorium Mercuriala. Przechodzimy zatem do katalogu testy i wydajemy komendę:

user@host testy $ hg init

W katalogu powinien pojawić się podkatalog .hg

Co teraz możemy zrobić? Dostępne komendy pojawią się po wydaniu komendy hg bez parametrów:

user@host testy $ 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

Jeśli chcemy uzyskać większą ilość komend, należy uruchomić polecenie
hg help.

Zobaczmy status naszego katalogu. Możemy napisać hg status, jednak można też zastosować skróconą wersję komendy:

user@host testy $ hg st
? dokumenty/do-zrobienia.txt
? notatki.txt

Widzimy, że system Mercurial widzi dwa pliki. Przed ich nazwami figuruje znak pytajnika, co oznacza, że Mercurial nie do końca wie, co to za pliki.

Dodawanie plików do repozytorium

Do dodawania plików do repozytorium służy komenda hg add [nazwa pliku], przy czym nazwa pliku jest parametrem opcjonalnym. Jeśli jej nie podamy – Mercurial doda wszystkie nieznane pliki w naszym katalogu i podkatalogach do repozytorium.
Dodajmy nasze pliki do repozytorium.

user@host testy $ hg add
adding dokumenty/do-zrobienia.txt
adding notatki.txt

podglądając status widzimy:

user@host testy $ hg st A dokumenty/do-zrobienia.txt A notatki.txt

Zatwierdzanie i weryfikowanie zmian

możemy w tym momencie zatwierdzić zmiany:

user@host testy $ hg commit

W tym momencie uruchomi się nam edytor w którym uzupełniamy pierwszą linię. Pozostałe zostały zasugerowane przez Mercuriala:

utworzone repozytorium

HG: Enter commit message.  Lines beginning with 'HG:' are removed.
HG: Leave message empty to abort commit.
HG: --
HG: user: Imie Nazwisko 
HG: branch 'default'
HG: added dokumenty/do-zrobienia.txt
HG: added notatki.txt

Jeśli chcemy którąś z tych linii pozostawić, powinniśmy usunąć HG: sprzed wpisu.

Po zapisaniu pliku, jeśli Mercurial znajdzie choć jedną linię opisu (czyli w naszym wypadku tekst „utworzone repozytorium”), zapisze to jako nową wersję (revision).

Możemy podejżeć zmiany:

user@host testy $ hg st
user@host testy $

Jak widać, nie ma żadnych. Zobaczmy zatem historię:

user@host testy $ hg log
changeset:   0:996ae301b615
tag:         tip
user:        Imie Nazwisko 
date:        Tue Oct 13 08:40:12 2009 +0200
summary:     utworzone repozytorium

user@host testy $

Dokonajmy jakiejś zmiany w pliku dokumenty/do-zrobienia.txt, powiedzmy, że teraz wygląda tak:

[x] Zrobić przelewy za październik
[ ] Podać do Energetyki stan licznika

Sprawdzając status widzimy:

user@host testy $ hg st
M dokumenty/do-zrobienia.txt
user@host testy $

Możemy też podejżeć co zostało zmienione:

user@host testy $ hg diff
diff -r 996ae301b615 dokumenty/do-zrobienia.txt
--- a/dokumenty/do-zrobienia.txt	Tue Oct 13 08:40:12 2009 +0200
+++ b/dokumenty/do-zrobienia.txt	Tue Oct 13 08:46:21 2009 +0200
@@ -1,2 +1,2 @@
-[ ] Zrobić przelewy za październik
+[x] Zrobić przelewy za październik
 [ ] Podać do Energetyki stan licznika
user@host testy $

Zatwierdzę teraz zmiany, sprawdzę status i historię:

user@host testy $ hg commit
user@host testy $ 

user@host testy $ hg st
user@host testy $ 

user@host testy $ hg log
changeset:   1:155c93da6b65
tag:         tip
user:        Imie Nazwisko 
date:        Tue Oct 13 08:48:01 2009 +0200
summary:     aktualizacja

changeset:   0:996ae301b615
user:        Imie Nazwisko 
date:        Tue Oct 13 08:40:12 2009 +0200
summary:     utworzone repozytorium

user@host testy $

Usuwanie plików z repozytorium

Teraz usunę plik notatki.txt:

user@host testy $ hg remove notatki.txt
user@host testy $ ls
dokumenty
user@host testy $

Zatwierdzę zmiany i zobaczę historię. Ograniczę sobie przy okazji historię do dwóch najaktualniejszych zmian:

user@host testy $ hg commit
user@host testy $ hg log -l 2
changeset:   2:ec95d15664fb
tag:         tip
user:        Imie Nazwisko 
date:        Tue Oct 13 08:51:36 2009 +0200
summary:     usunięte notatki

changeset:   1:155c93da6b65
user:        Imie Nazwisko 
date:        Tue Oct 13 08:48:01 2009 +0200
summary:     aktualizacja

user@host testy $

Zdecydowałem jednak, że przywrócę plik notatki. Cofnę zatem ostatniego commita komendą rollback, zobaczę jaki
jest status i przywrócę plik notatki.txt:

user@host testy $ hg rollback
rolling back last transaction
user@host testy $ hg log
changeset:   1:155c93da6b65
tag:         tip
user:        Imie Nazwisko 
date:        Tue Oct 13 08:48:01 2009 +0200
summary:     aktualizacja

changeset:   0:996ae301b615
user:        Imie Nazwisko 
date:        Tue Oct 13 08:40:12 2009 +0200
summary:     utworzone repozytorium

user@host testy $ hg st
R notatki.txt
user@host testy $ ls
dokumenty
user@host testy $ hg revert notatki.txt
user@host testy $ ls
dokumenty  notatki.txt
user@host testy $

Myślę, że jako wprowadzenie, taki opis wystarczy :)

Drażniący dzwięk podczas pracy na baterii w Ubuntu

intel-core2-duoLinux Ubuntu jest na tą chwilę chyba najlepszą Linuxową dystrybucją na desktopa. Jest ona jednak nadal świeża i zdarza się, że czasami ma pewne problemy z nowszym sprzętem.

Jednym z takich problemów z którym się zetknąłem jest drażniący pisk czyli dźwięk o wysokiej częstotliwości (ang. high-pitch noise) podczas pracy notebooka na zasilaniu bateryjnym. Przyczyną tego dzwięku jest wyższy tryb ACPI odpowiedzialny z zarządzaniem zasilaniem i temperaturą. Więcej szczegółów technicznych można znaleźć na stronach Intela opisujących C-State Architecture.

Mój notebook ma procesor Intel Centrino (Core2) Duo T5600 i niestety ten problem go dotyczy.

Stare rozwiązanie stosowane w Ubuntu 8.04

W jądrze systemu ustawiona jest możliwość zmiany tego parametru podczas pracy systemu (parametr CONFIG_ACPI_PROCESSOR=y).

Aby dźwięk ustał należy uruchomić komendę:

echo 2 >/sys/module/processor/parameters/max_cstate

Można pokusić się o dodanie takiego skryptu do /etc/acpi.d który ten problem rozwiąże, ale lepiej zastosować następne rozwiązanie.

Szczegółowe informacje można znaleźć tutaj.

Nowe rozwiązanie dla Ubuntu 8.04, 8.10 i 9.04 oraz prawdopodobnie pozostałych

W konfiguracji grub’a, do parametrów kernela należy dodać processor.max_cstate=2, przykładowy wpis:

title           Ubuntu 9.04, kernel 2.6.28-15-generic
root            (hd0,1)
kernel          /vmlinuz-2.6.28-15-generic root=/dev/mapper/sys-root ro quiet splash processor.max_cstate=2
initrd          /initrd.img-2.6.28-15-generic
quiet

Rozwiązanie to ma tą wadę – trzeba o nim pamiętać i uzupełniać w konfiguracji gruba nowe kernele o ten parametr. Jeśli to rozwiązanie jest dla Ciebie przydatne to zachęcam do oznaczenia go sobie w Google Readerze jako ulubione, bo kto wie, być może będzie potrzeba aby do niego wrócić :)

Mercurial w Eclipse IDE

eclipse_logoŚrodowisko programistyczne Eclipse jest jednym z najlepszych spośród tych dostępnych za darmo. Osobiście preferuję vim’a, ale większość programistów przywykło do większych luksusów :)

Eclipse jest dostępny zarówno pod Windows jak i pod Linuxem. Jego instalacja w systemie Microsoftu jest dość prostą czynnością.

W Linux Ubuntu, Eclipse jest niby też dostępny w standardowej dystrybucji, ale mnie osobiście nigdy nie udało się go z sukcesem uruchomić ze standardowej paczki. Tym z Was, którzy chcą z niego skorzystać pod Ubuntu zachęcam do pobrania Eclipse z ich strony, następnie rozpakowanie go do docelowego katalogu, a na koniec ręczne stworzenie skrótu.

Eclipse zostało stworzone w Javie i w zamyśle jest IDE służącym do tworzenia aplikacji właśnie w tym języku. Dzięki zaawansowanemu systemowi rozszerzeń możliwe jest jednak jego wykorzystanie do tworzenia aplikacji w innych językach. Właśnie ten mechanizm umożliwi nam również uzupełnienie Eclipse’a o system kontroli wersji.

Aby mieć możliwość z korzystania z Mercuriala, konieczna będzie jego instalacja w systemie. Dla systemu Microsoft Windows można go pobrać z tej strony. Dodatkowo warto zainstalować TortoiseHG. Kroki opisujące instalację pod Linux Ubuntu wraz z TortoiseHG opisałem tutaj.

Wtyczka Mercurial-Eclipse

Aby za pomocą środowiska Eclipse posługiwać się systemem kontroli wersji Mercurial, konieczne będzie dodanie jego repozytorium do dostępnych źródeł w Eclipse. W tym celu wybieramy Help / Install new software… / Add… i podajemy:

name: Mercurial Eclipse
location: http://www.vectrace.com/eclipse-update/

Osobiście testowałem plugina w wersji 1.4.1286 – zainstalował się i działał prawidłowo.

Po zainstalowaniu, plugin poprosi o restart środowiska Eclipse. Kiedy już Eclipse zacznie ponownie działać, powinniśmy mieć dostęp do nowej funkcjonalności.

Dość ważną kwestią jest na początku zaglądnięcie do ustawień Mercuriala – Window / Preferences, wybieramy Team / Mercurial. Należy tutaj podać Mercurial Username w postaci Imię nazwisko <adres@email>. Zaleciłbym również zaznaczenie opcji Automatically associate Mercurial with new projects containing hg repository.

Jeśli chodzi o zakładanie repozytorium dla istniejącego już projektu – w Eksploratorze workspace wybieramy projekt, naciskamy prawy klawisz myszy, następnie Team / Share project – tam wybieramy Mercurial i na następnej stronie wizarda Use project root.

Jeśli repozytorium już było założone – trzeba zaglądnąć do projektu podobnie jak przy tworzeniu nowego repozytorium, wybrać Share project, a na końcu Use existing .hg repository.

W tym momencie możemy już korzystać z bardziej urozmaiconej zawartości popup menu Team :)

eclipse-mercurial

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 :)

Kontrolka button w jQuery UI

Ogólny trend rozwoju interfejsów aplikacji spycha je coraz bardziej w kierunku webowym. Zmieniają się dzięki temu developerzy i sposób postrzegania aplikacji. Lekkość aplikacji zaczyna być w cenie. Krowiaste applety Javy, przerośnięte Flashe zaczynają powoli ustępować lżejszym rozwiązaniom. Jednym z nich jest jQuery UI.

Czym jest jQuery UI

jQuery UI to mini-framework dla aplikacji po stronie klienta. Powstał on przy projekcie jQuery, a zespoły tych projektów są dość mocno zintegrowane – świadczy o tym m.in About jQuery, wspólna dokumentacja oraz tempo rozwoju. Zespół UI jest dodatkowo zrzeszony w Filament Group, pod szyldem którego publikuje całkiem często dość ciekawe artykuły bądź to opisując UI, Aria, bądź uzupełniając kontrolki o nowe i jednocześnie je omawiając. Na ich stronie nie znalazłem RSS’a, ale o nowościach zawsze wspominają na swoim Twitterowym profilu filamentgroup – którego kanał RSS sobie zasubskrybowałem w moim Google Readerze – rozwiązanie spisuje się dobrze :)

Za pomocą jQuery UI możemy przygotować graficzny interfejs naszej aplikacji webowej. Mamy do dyspozycji bibliotekę funkcji rozszerzających standardowe efekty o nowe, własne kontrolki, tematy graficzne (które możemy rozszerzać o własne) wraz z rewelacyjnie narysowanymi ikonkami, dostosowanymi do każdego z tematów. Żeby zobaczyć jak to wszystko wygląda w różnych standardowych tematach graficznych – przykładowy podgląd ThemeRoller.

Standardowa baza kontrolek (widget’ów) jest niewielka – kontrolka daty, okno dialogowe, progress bar, taby. Dodatkowych można się doszukać na SVN’ie w labsach albo poprzez wspomnianą wyżej stronę filamentgroup.

Wprowadzenie do jQuery UI w Twitterowym stylu ;)

Do html’a naszej aplikacji załaczamy CSS z wybranego tematu jQuery UI oraz skrypty jQuery i jQuery UI – przykładowo:

<link href="theme/start/jquery-ui.css" type="text/css" rel="stylesheet"/>
<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jquery-ui.js" type="text/javascript"></script>

Mamy teraz dostępną całą paletę styli i narzędzi jQuery UI. Zarówno jedno jak i drugie jest bardzo łatwo rozszerzalne.

Jak to rozbudować?

Stworzymy sobie przycisk (ang. button) – przyda się nam omówiona w poprzednim wpisie biblioteka do generowania dynamicznego CSS. Tak mniej więcej wygląda początkowy szkielet aplikacji:

$(document).ready(function() {

    var self = this;
    self.css = new CSS('dynamic');
    //include('button.js');

});

Jak wszyscy wiemy, w JavaScript nie ma include. Można zrobić tak aby był, ale o tym napiszę niebawem. Póki co w jego miejsce możemy wstawić kod właściwej kontrolki.

// ui.button

self.css.replace('.cx-button',{
	'outline':'0',
	'margin':'0 4px 0 0',
	'padding':'.4em 1em',
	'text-decoration':'none !important',
	'cursor':'pointer',
	'position':'relative',
	'text-align':'center',
	'zoom':'1'
});
// remove extra button width in IE
self.css.replace('a.cx-button',{
	'float':'left'
});

(function($) {

$.widget('ui.button', {
	_init: function() {
		this.element
			.addClass('cx-button ui-state-default ui-corner-all')
			.hover(function() {
				if (! $(this).hasClass('ui-state-disabled')) {
					$(this).addClass('ui-state-hover');
				}
			},function() {
				$(this).removeClass('ui-state-hover');
			})
			.mousedown(function() {
				if (! $(this).hasClass('ui-state-disabled')) {
					$(this).addClass('ui-state-active');
				}
			})
			.mouseup(function() {
				$(this).removeClass('ui-state-active');
			})
			.disableTextSelect();
	},
	disabled: function() {
		this.element.addClass('ui-state-disabled');
	},
	enabled: function() {
		this.element.removeClass('ui-state-disabled');
	},
	destroy: function() {
		this.element
			.removeClass('cx-button ui-state-default ui-corner-all ui-state-hover ui-state-active ui-state-disabled')
			.unbind('mouseenter mouseleave');
	}
});

})(jQuery);

Takie właśnie przyciski zostały przeze mnie użyte na przykładowej stronie flatforma.appspot.com – należy najpierw przekliknąć się w accordeonie po lewej, a wtedy pojawią się przyciski, które dodają nowe zakładki. Dla leniwych zrzut ekranu:

flatforma-screenshot

Zachęcam do analizy przedstawionego w tym tekscie kodu – można go uznać za bazowy szkielet do tworzenia kontrolek.

Temat został szerzej opisany na stronach Filament Group:
Styling Buttons and Toolbars with the jQuery UI CSS Framework

Dynamiczny CSS z poziomu JavaScript

Jak zapewne większość developerów używających JavaScriptu, mam swoją własną biblioteczkę skryptów oraz dodatkowych narzędzi która stanowi dla mnie formę bootstrapu do tworzenia nowej aplikacji. Z każdym projektem biblioteczka rośnie i staje się bardziej dojżała. Przymierzam się od jakiegoś czasu do opublikowania całego bootstrapu, bo uważam, że jest warty uwagi, ale jakoś nie miałem do tej pory zbytnio na to czasu.

W tym tekscie poniżej przedstawie jeden ze skryptów który należy do mojej biblioteczki. Załączam go standardowo do każdej swojej aplikacji, szczególnie teraz, kiedy zacząłem przygotowywać swoje własne rozszerzenia JQuery UI.

Swobodniejsze podejście

Bardzo lubie dbać o to, aby struktura aplikacji była zwięzła, łatwa do ogarnięcia oraz estetyczna. W dobie Javascriptu mamy coraz to większe możliwości aby samemu sobie ustrukturyzować aplikacje.

Wielu doświadczonych developerów zakłada, sztywny podział – czyli, że wygląd powinien być w plikach CSS, funkcjonalność w JavaScript, a dane na serwerze. Oczywiście z trzecim się zgadzam, ale z podziałem na wygląd i funkcjonalność – już nie koniecznie.

Przykładowo – w naszym pliku CSS wykorzystujemy -moz-border-radius i -webkit-border-radius, dzięki czemu mamy zaokrąglone rogi wszędzie, poza Internet Explorerem. Musimy dodać zaokrąglone rogi w Explorerze. Co począć? Nabrudzić sobie w CSSie? A później za pomocą JQuery sprawdzić wersję przeglądarki i ustawić te klasy? Można i tak. Możemy jednak sprawdzić czy mamy do czynienia z IE i jeśli tak – dopiero wtedy dodać nowe klasy CSS.

Dynamiczne ładowanie lub generowanie CSS nie jest czymś bez czego nie możnaby się obyć, ale dzięki warstwom abstrakcji możemy sobie nie śmiecić w plikach z layoutem.

Poniższy kod działa bez problemu w każdej zaawansowanej przeglądarce WWW oraz w Internet Explorerze :)

/*
 * CSS Gen
 * Copyright (c) 2009 nme.pl
 * Dual licensed under MIT and GPL.
 *
 * from scratch, based on article http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
 *
 * Example usage:
 *
 * var css = new CSS('dynamic'); // Create new stylesheet
 * css.replace('.asd',{'color':'green'}); // If there is no asd class definition yet, it will be created
 *
 */

var CSS = function(name,url) {
	if (name === undefined) {
		return false;
	}
	var styles = document.styleSheets;
	if (!styles) {
		return null;
	}
	this.find_style = function(name) {
		if (styles) {
			for (var i in styles) {
				if (styles[i].title === name) {
					return styles[i];
				}
			}
		}
		return null;
	};
	var create_style = function(name) {
		var node = document.createElement('style');
		node.type = 'text/css';
		node.rel = 'stylesheet';
		node.media = 'screen';
		node.title = name;
		document.getElementsByTagName(”head”)[0].appendChild(node);
		return node;
	};
	var load_style = function(name,url) {
		var node = document.createElement('link');
		node.type = 'text/css';
		node.rel = 'stylesheet';
		node.href = url;
		node.media = 'screen';
		node.title = name;
		document.getElementsByTagName(”head”)[0].appendChild(node);
		return node;
	};
	var style = this.find_style(name);
	if (style === null) {
		if (url === undefined) {
			style = create_style(name);
		} else {
			style = load_style(name,url);
		}
	}
	var sheet = null;
	if (style !== null) {
		if (style.styleSheet !== undefined) {
			sheet = style.styleSheet;
		} else {
			sheet = style.sheet;
		}
	}
	this.index = function(search) {
		search = search.toLowerCase();
		var i=0;
		var rule=null;
		if (sheet === undefined) {
			return null;
		}
		do {
			if (sheet.cssRules) {
				rule = sheet.cssRules[i];
			} else if (sheet.rules) {
				rule = sheet.rules[i];
			}
			if (rule) {
				if (rule.selectorText.toLowerCase() == search) {
					return i;
				}
				i++;
			}
		} while (rule);
		return null;
	};
	this.rule = function(search) {
		search = search.toLowerCase();
		var i=0;
		var rule=null;
		if (sheet === undefined) {
			return null;
		}
		do {
			if (sheet.cssRules) {
				rule = sheet.cssRules[i];
			} else if (sheet.rules) {
				rule = sheet.rules[i];
			}
			if (rule) {
				if (rule.selectorText.toLowerCase() == search) {
					return rule;
				}
				i++;
			}
		} while (rule);
		return null;
	};
	this.add = function(search, body) {
		if (sheet === undefined) {
			return null;
		}
		if (typeof body === 'object') {
			var rules='';
			for (var i in body) {
				if (body.hasOwnProperty(i)) {
					rules = rules+i+':'+body[i]+';\n';
				}
			}
			body = rules;
		}
		if (!this.rule(search)) {
			if (sheet.cssRules) {
				sheet.insertRule(search+' {'+body+'}', 0);
			} else if (sheet.rules) {
				sheet.addRule(search, body);
			}
		}
		return this.rule(search);
	};
	this.remove = function(search) {
		var i = this.index(search);
		if (i !== null) {
			if (sheet.cssRules) {
				sheet.deleteRule(i);
			} else if (sheet.rules) {
				sheet.removeRule(i);
			}
		}
	};
	this.replace = function(search, body) {
		this.remove(search);
		return this.add(search,body);
	};
};

Jak tego używać? Na początku przygotowujemy nowy, dynamiczny stylesheet:

var css = new CSS('dynamic');

Jeśli klasa asd nie została jeszcze zdefiniowana, robimy to teraz oraz ustawiamy jej następujące atrybuty:

css.replace('.asd',{'color':'green'});

Pseudoklasa CSS umożliwia również dynamiczne ładowanie CSS’ów czy usuwanie poszczególnych reguł styli. W razie wątpliwości – zachęcam do zaglądania do kodu oraz do kontaktu ze mną :)

Przykładową aplikacją, która dość intensywnie korzysta z tego fagmentu kodu jest http://mlchat.appspot.com – czat, który uruchomiłem na Google App Engine, który jednak zarzuciłem, bo nie znalazłem już sił po stworzeniu go na jego odpluskwianie i promocję ;) Wygląd aplikacji miał być z zamierzenia łudząco podobny do Google Web Toolkit‘u i co za tym idzie takich aplikacji jak Google Reader, ale tak naprawdę było to JQuery i trochę pracy :) Zachęcam do poklikania – ludzi tam raczej nie ma, jak sugerowałyby liczby, to tylko duchy – stare sesje się nie kasują jak powinny ;)

Niech ktoś to zrobi pod Windowsem :P

Wyobraźmy sobie, że surfujemy pod Windowsem, natrafiamy na coś takiego…

diox-before

…a pod Linuxem wystarczy jeden skrót klawiszowy i mamy taki efekt:

diox-after

:) Aby móc to robić potrzebny jest uruchomiony Compiz. Tą funkcjonalność (Dostępność / Negatyw) i inne najwygodniej skonfigurować, lub włączyć za pomocą System / Preferencje / Menadżer ustawień CompizConfig. Jeśli takowego nie posiadamy – możemy go sobie zainstalować następującą komendą:

sudo aptitude install compizconfig-settings-manager

Skróty negatyw danego okna lub negatyw ekranu są wbrew pozorom dość przydatne, zwłaszcza wieczorem, podczas czytania ebooków.

Aniołek pochodzi ze strony http://diox.mylog.pl ;)

Instalacja Chromium w Ubuntu Jaunty

chromium-cornerPrzeglądarka WWW giganta z Mountain View o nazwie Google Chrome jest dostępna póki co jedynie dla Windows. Była ona tworzona w pośpiechu i tajemnicy. Kiedy została wydana, Google jednocześnie otworzyło otwartoźródłowy projekt o nazwie Chromium. Składają się na niego między innymi własny fork silnika do renderingu stron – WebKit czy stworzony przez Google – bardzo wydajny silnik Javascriptu o nazwie V8. Dzięki otwarciu kodu, umożliwiony został szybszy rozwój oraz poprawę tego, co było zrobione na szybko oraz tego, czego najbardziej brakuje. Do projektu Chromium dołączyło wielu developerów spoza Google, którzy bardzo aktywnie zaczęli podrzucać łaty, mające na celu umożliwienie uruchamianie Chrome pod Linuxem. Prace się jeszcze nie skończyły, ale już teraz możemy uruchomić wersję developerską – Chromium, na własnym Linuksie. W tym tekscie pokażę, jak tego dokonać pod Ubuntu 9.04.

Dlaczego chcę o tym pisać, kiedy lada dzień ma wyjść Ubuntu 9.10? Z przyczyn archiwalnych, ponieważ w Ubuntu 9.10 będzie się to robiło już inaczej, znacznie prościej. Nie oznacza to, że jeśli zainstalujemy teraz Chromium, będziemy musieli wykonywać nowe kroki po aktualizacji do 9.10 – Chromium będzie działał jak dawniej.

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

sudo -i

# Dodajemy repozytorium Chromium z Ubuntu Launchpad PPA

cat >/etc/apt/sources.list.d/chromium.list << EOF
deb http://ppa.launchpad.net/chromium-daily/ppa/ubuntu jaunty main
deb-src http://ppa.launchpad.net/chromium-daily/ppa/ubuntu jaunty main
EOF

# Dodajemy klucz do pakietów z repozytorium Chromium

gpg --keyserver hkp://wwwkeys.eu.pgp.net --recv-keys 5A9BF3BB4E5E17B5
gpg --export --armor 5A9BF3BB4E5E17B5 | sudo apt-key add -

# Aktualizujemy repozytoria i instalujemy Chromium wraz z wersją Polską :) 

aptitude update
aptitude install -y chromium browser chromium-browser-l10n

Po pierwszym uruchomieniu, Chromium pyta o ustawienie go jako domyślnej przeglądarki oraz import z Firefoxa - ustawienia jako domyślnej przeglądarki póki co nie polecam, a import u mnie to jeszcze nie działa.

Żeby było bardziej pod kolor, warto nacisnąć Ustawienia, Opcje, zakładka Prywatne rzeczy, Motywy: Ustaw motyw na GTK+.

Obsługa Flasha i innych wtyczek jest narazie eksperymentalna, ale mnie przynajmniej nic się nie jeszcze zawiesiło - YouTube można oglądać. Aby działał Flash, skrót do Chromium należy uruchamiać z parametrem --enable-plugins:

chromium-browser --enable-plugins

Przeglądarka działa lekko i przyjemnie - w moim odczuciu - o wiele lżej niż Firefox, którego ostatnio coraz bardziej nie mogę znieść - nie ważne czy to pobranego bezpośrednio ze stron Mozilli, czy tego z repozytoriów Ubuntu. Chromium jest też w moim odczuciu stabilniejszy niż Midori, którego do tej pory używałem do testowania moich aplikacji webowych pod kątem WebKit'a pod Linuxem. Spróbujcie, warto.

dostępna

Podgląd strony do pliku jpg?

Poruszenie w Internecie tematyką wprowadzania cenzury, o której ostatnio pisałem, podsunęło mi pewien pomysł, który mam nadzieję niebawem Wam przedstawię. W między czasie uznałem, że w projekcie przyda mi się funkcjonalność zautomatyzowanego robienia podglądów (thumbnail) stron WWW do obrazków. Miałem przy tym całkiem dużo frajdy i mam nadzieję, że efekty mojej pracy komuś się to przydadzą.

Serwisów które przygotowują podgląd stron jest w sieci niewiele, a takich, które za darmo udostępniają wygodne API, nie mają śmiesznie małych limitów przygotowań podglądów nie znalazłem. Napisałem sobie takie narzędzie sam. Jeśli ktoś chce wykorzystać ten kod – proszę bardzo – proszę tylko o informację gdzie jest on dostępny oraz liczę na darmowy dostęp ;)

Twoja własna mini-przeglądarka

Jak szybko zrobić sobie przeglądarkę z obsługą Flasha, JavaScriptu i tych wszystkich urozmaiceń? Wystarczyło mi kilkanaście minut :) Zdecydowałem się na użycie GTK (domyślnego zestawu kontrolek dla m.in. Gnome) oraz Webkit’a – silnika do renderowania wykorzystywanego w takich przeglądarkach jak Safari (Mac OS), Google Chrome (Windows) czy Midori (Linux):

#!/usr/bin/env python

url = 'http://www.nme.pl'
size = [800, 600]

import gtk,webkit
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.set_default_size(size[0], size[1])
window.connect('destroy', gtk.main_quit)
preview = webkit.WebView()
preview.open(url)
scrolled = gtk.ScrolledWindow()
scrolled.add(preview)
window.add(scrolled)
window.show_all()
gtk.main()

Właściwy podgląd strony

Kod trzeba było trochę rozbudować. Konfiguracja jest w klasie conf. Uruchamiając kod w pierwszym parametrze należy podać adres strony (nie zapominając o nazwie protokołu, np. http://www.nme.pl).

w katalogu w którym znajduje się skrypt trzeba założyć podkatalog thumb, w którym zapisywane będą obrazki jpg.

shot.py:

#!/usr/bin/env python

__author__ = 'mw AT nme.pl'
__version__ = '1.0'
__date__ = 'wto, 25 sie 2009, 09:15:48 CEST'

import pygtk
pygtk.require('2.0')
import gtk,gtk.gdk,gobject
import webkit
import hashlib
import os,sys

class conf:
    show_scrollbars = True
    show_progress = True
    script_timeout_secs = 15
    size = [1024, 768]

class Snapshot:

    def load_progress_changed(self, view, progress):
        print '%d%%' % progress

    def load_finished(self, view, frame):
        print 'loading finished'
        w = gtk.gdk.get_default_root_window()
        wp = self.window.get_position()
        ws = self.window.get_size()
        sz = w.get_size()
        pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,False,8,ws[0],ws[1])
        pb = pb.get_from_drawable(w,w.get_colormap(),wp[0],wp[1],0,0,ws[0],ws[1])
        if (pb != None):
            pb.save('thumbs/'+self.file+'.jpg','jpeg',{'quality':'95'})
        gtk.main_quit()

    def __init__(self, url=None):

        self.url = url
        self.file = hashlib.sha512(url).hexdigest()
        self.overtime = False
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_default_size(conf.size[0], conf.size[1])
        self.window.set_decorated(False)
        self.window.connect('destroy', gtk.main_quit)
        self.preview = webkit.WebView()
        self.preview.open(self.url)
        if conf.show_progress:
            self.preview.connect('load-progress-changed',self.load_progress_changed)
        self.preview.connect('load-finished',self.load_finished)
        self.scrolled = gtk.ScrolledWindow()
        if conf.show_scrollbars:
            self.scrolled.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC
            self.scrolled.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC
        else:
            self.scrolled.props.hscrollbar_policy = gtk.POLICY_NEVER
            self.scrolled.props.vscrollbar_policy = gtk.POLICY_NEVER
        self.scrolled.add(self.preview)
        self.window.add(self.scrolled)
        self.window.show_all()
        gobject.timeout_add(conf.script_timeout_secs*1000, self.timeout)
        gtk.main()

    def timeout(self):
        gtk.main_quit()
        sys.exit(1)

if len(sys.argv)==2:
    Snapshot(url=sys.argv[1])
else:
    print >> sys.stderr, "Usage: %s url\n" % sys.argv[0]
    sys.exit(1)

Wrapper do generowania właściwego poglądu

Skrypt działa, ale wymaga działania X’ów, coś nam jeszcze wyświetla i zapisuje obrazek w skali 1:1. Da się to zrobić w tle. Potrzebujemy do tego dwóch narzędzi – convert z pakietu imagemagick oraz pakietu xvfbVirtual Framebuffer ‘fake’ X server. Mając już te narzędzia – całą robotę zrobi za nas skrypt o nazwie snap:

#!/bin/bash

state=0
plik=`python -c "import hashlib; print hashlib.sha512('$1').hexdigest()+'.jpg'"`
[ -e "thumbs/$plik" ] || {
	xvfb-run -a --server-args='-screen 0 1024x768x16' python shot.py $1
}
state=$?
[ "$state" -eq "0" ] && {
	[ -e "thumbs/$plik" ] && {
		convert -scale 310x -quality 75 thumbs/$plik thumbs/$plik
	} || {
		exit 1
	}
} || {
	exit 1
}

skryptowi nadajemy prawa do jego uruchamiania:

chmod +x snap

i uruchamiamy:

./snap http://www.nme.pl

w katalogu thumb odnajdujemy gotowego jpg’a (jego nazwa jest hashem sha512 URL’a). Wygląda to tak:

www.nme.pl-thumb

Powiązane, teksty w języku angielskim:

  1. Podobne rozwiązanie w oparciu o Qt, warto obejrzeć.
  2. Przykładowy opis jak można uruchomić serwis do generowania podglądów w oparciu o Django z nawiązaniem do gotowych już rozwiązań.

« Nowsze wpisyStarsze wpisy »