Zapamiętywanie stanu AJAX-owych formularzy w Django

Problem, który chciałbym poruszyć w tym wpisie, dotyczy formularzy, których wartości ładowane są AJAX-em i których stan ma być zapamiętany po przeładowaniu strony. Załóżmy, że mamy formularz do wyszukiwania jakichś elementów przypisanych do jednostek terytorialnych. Możemy wybrać województwo, powiat, gminę, miasto. Wszystkie te wartości są słownikowe (zaimplementowany TERYT). Ogólnie w Polsce jest dużo gmin, a jeszcze więcej miast. Ładowanie wszystkich miast do select-a nie wchodzi zatem w rachubę, więc naturalnym rozwiązaniem jest stosowanie AJAX-a - jak wybiorę województwo, ładują się powiaty do selecta, jak wybiorę powiat, ładują się gminy do selecta itd. Jednak co, jeżeli wybiorę już sobie województwo, powiat, gminę i miasto i kliknę szukaj? Po przeładowaniu strony formularz wyszukiwania będzie zawierał tylko listę województw, a reszta się wyczyści. Jest jednak metoda, którą można sobie z tym poradzić.

Formularz w pierwotnej postaci wygląda tak:

class SzukajFirme(forms.Form):  
        wojewodztwo = forms.ChoiceField(choices=pobierz_wojewodztwa(), required=False, label="Województwo")
        powiat = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Powiat")
        gmina = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Gmina")
        miejscowosc = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Miejscowość")

Całość można rozwiązać na poziomie formularza (widziałem już rozwiązania w których używano widoku). Aby załadować wybrane wartości, musimy dostać się do danych przesyłanych przez POST. Jest to możliwe poprzez odczytanie w metodzie __init__() argumentu przekazanego do formularza. Jak już mamy dane z post-a, to reszta jest prosta - wystarczy pobrać kolejno poszczególne poziomy struktury podziału terytorialnego. A oto jak to wygląda:

class SzukajFirme(forms.Form):  
        def __init__(self, *args, **kwargs):
                super(SzukajFirme, self).__init__(*args, **kwargs)
                try:
                        post = args[0]
                        if post['wojewodztwo']:
                                pow = Powiaty.objects.filter(wojewodztwo=post['wojewodztwo']).all()
                                self.fields['powiat'].choices = [('','-----'),] + map(lambda x: (x.id, x.nazwa), pow)           
                        else:
                                self.fields['powiat'].choices = (('','-----'),)
                        if post['powiat']:
                                gmi = Gminy.objects.filter(powiat=post['powiat']).all()
                                self.fields['gmina'].choices = [('','-----'),] + map(lambda x: (x.id, x.nazwa), gmi)    
                        else:
                                self.fields['gmina'].choices = (('','-----'),)          
                        
                        if post['gmina']:
                                miej = Miejscowosci.objects.filter(gmina=post['gmina']).all()
                                self.fields['miejscowosc'].choices = [('','-----'),] + map(lambda x: (x.id, x.nazwa), miej)     
                        else:
                                self.fields['miejscowosc'].choices = (('','-----'),)
                except:
                        pass            
                        
        wojewodztwo = forms.ChoiceField(choices=pobierz_wojewodztwa(), required=False, label="Województwo")
        powiat = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Powiat")
        gmina = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Gmina")
        miejscowosc = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Miejscowość")

Po przeładowaniu strony, w formularzu załadują się wybrane wcześniej pozycje, wraz z elementami podrzędnymi (np. jak wybrano województwo, to będą załadowane powiaty). Osobiście najbardziej podoba mi się to, że nie trzeba angażować do tego żadnego widoku, wszystko można zrobić na poziomie formularza, to formularz sam martwi się o swoje dane.

Bot SMS pod Linuksem

Ostatnio w firmie pojawił się temat stworzenia SMS bota. Zacząłem więc szukać rozwiązań. Jeden z pomysłów, to postawienienie komputera z Linuksem na pokładzie, podłączenie do niego telefonu z ofertą, w której jest miliard darmowych SMS-ów i uruchomienie czegoś magicznego, co zajmie się odbieraniem i wysyłaniem wiadomości. I to magiczne coś właśnie zamierzam opisać.

Rozwiązanie które można zastosować, okazało się prostsze niż myślałem. Można użyć do tego świetnego programu, który jest dostępny zarówno pod Linuksem jak i Windowsem - mowa o Gammu. Jest to program, który komunikuje się z telefonem, lub modemem i służy do różnego rodzaju cznności, takich jak odbieranie/wysyłanie SMS-ów, zarządzanie kontaktami, zadaniami, kalenarzem itp. Ja oczywiście skupię się na odbieraniu i wysyłaniu SMS-ów.

Tak więc do komunikacji użytkownika z botem posłużę się Gammu - to już ustaliliśmy. Żeby było ciekawiej, Gammu posiada demona do wysyłania i odbierania SMS-ów - gammu-smsd. Jako backend do przechowywania danych można użyć zwykłych plików lub bazy danych (obsługiwany jest MySQL, PostgreSQL i SQLite). Pliki odradzam, bo to nieco ogranicza funkcjonalność - przykładowo nie jest możliwe wysyłanie wiadomości przy użyciu polecenia gammu-smsd-inject lub nie można przekazać żadnego id wiadomości do skryptu, który można uruchomić po otrzymaniu wiadomości (o tym w dalszej części). Lepiej zatem skorzystać z bazy danych. Ja wybrałem PostgreSQL.

Konfiguracja Gammu oraz SMSD

Najpierw trzeba zacząć od komunikacji Gammu z telefonem. Mój telefon to Sony Ericsson k800i. Pomijam fakt, że trzeba podłączyć telefon do komputera przez kabel USB ;) Poniżej zamieszczam źródła plików konfiguracyjnych:

~/.gammurc

[gammu] 
port = /dev/ttyACM0 
model = 
connection = at19200 
synchronizetime = no 
logfile = 
logformat = errorsdate 
use_locking = 
gammuloc =

/etc/gammu_smsdrc

[gammu] 
port = /dev/ttyACM0 
model = 
connection = at19200 
synchronizetime = no 
logfile = 
logformat = errorsdate 
use_locking = 
gammuloc = 

[smsd] 
Service = pgsql 
LogFile = syslog 
DebugLevel = 255 
DeliveryReport = log 
RunOnReceive = /home/jaro/bin/sms_trigger.py 
User = uzytkownik_bazy 
Password = haslo_bazy 
PC = localhost 
Database = gammu

Poprawność konfiguracji połączenia z telefonem można sprawdzić przez polecenie gammu identify. U mnie wyjście wyglądało tak:

# gammu identify 
Manufacturer : Sony Ericsson 
Model : K800i (AAD-3022031-BV) 
Firmware : R8BF003 080130 2133 CXC1250212_ORANGE_WI 
IMEI : WOLE_NIE_POKAZYWAĆ 
Kod produktu : AAD-3022031-BV 
SIM IMSI : WOLE_NIE_POKAZYWAĆ

Przed uruchomieniem demona smsd, trzeba utworzyć odpowiednie tabele w bazie danych. W dokumentacji znajduje się przykładowy plik ze skryptem SQL, który tworzy te tabele. U mnie znajduje się on w lokalizacji: /usr/share/doc/gammu/examples/sql/pgsql.sql Wystarczy uruchomić skrypt i voila!

Skrypt (bot)

W pliku gammu_smsdrc jest opcja RunOnReceive, która, jak się można domyślić, wskazuje skrypt uruchamiany po odebraniu wiadomości. Skrypt jest uruchamiany z parametrem, który zawiera id wiersza w bazie danych w tabeli inbox. Wiersz zawiera dane przesłanej wiadomości. Napisałem w Pythonie przykładowy skrypt, który odczytuje odebraną wiadomość, sprawdza jaki tekst wysłano i na jego podstawie wysyła odpowiedź.

#!/usr/bin/python

import psycopg2 as db, sys, os

db_user = 'nazwa_uzytkownika'
db_password = 'haslo_do_bazy'
db_host = 'localhost'
db_name = 'gammu'


def main(argv=None):
        try:
                sms_id = sys.argv[1]
        except IndexError:
                print 'Za malo parametrow'
                sys.exit()
        
        try:
                conn = db.connect("dbname=%s user=%s password=%s host=%s" % (db_name, db_user, db_password, db_host))
        except:
                print 'Blad podczas polaczenia z baza'
                
        if not sms_id.isdigit():
                print 'Niepoprawny parametr'
                sys.exit()
        
        cur = conn.cursor()
        cur.execute("SELECT * FROM inbox WHERE id=%s" % sms_id)
        
        row = cur.fetchone()
        
        if not row:
                print 'Nie znaleziono wiadomosci sms o podanym id'
                sys.exit()
                
                
        rcp_num = row[3]
        command = row[8].strip().lower()
        
        if command == 'jezyk':
                response = "Python"
        elif command == 'system':
                response = "Linux"
        else:
                response = "Nieznana komenda"
        
        
        os.system("gammu-smsd-inject TEXT %s -text '%s'" % (rcp_num, response)) 
        


if __name__ == '__main__':
        main()
        

Finał - uruchomienie demona

Wreszcie przechodzimy do puenty - uruchomienia demona. Wystarczy krótkie:

gammu-smsd --daemon

I w ten oto sposób, telefon jest gotowy do odbierania i wysyłania wiadomości.

iPlus pod Arch Linux

Sprzęt

Kwestia uruchomienia iPlusa pod Linuksem okazała się prostsza niż myślałem. Zaczęło się od tego, że modem PCMCIA Option GT MAX 3G, z którego korzystam, jest na dzień dobry rozpoznawany przez jądro. Automatycznie ładuje się moduł nozomi i pojawiają się urządzenia /dev/noz0-3. Jeden problem z głowy. Dla sprawdzenia można wpisać polecenia:

$ pccardctl status
Socket 0:
  3.3V 32-bit PC Card
$ lspci | grep Option
03:00.0 Network controller: Option N.V. Qualcomm MSM6275 UMTS chip

Dla bezpieczeństwa polecam przed wyciągnięciem użyć polecenia

# pccardctl eject

Oprogramowanie

Do zarejestrowania się w sieci przydatny będzie program comgt, a do połączenia wvdial i ppp.

# pacman -S comgt wvdial ppp

Ten ostatni prawdopodobnie jest już w systemie.

Konfiguracja

Najpierw należy edytować lub utworzyć plik wvdial.conf:

[Dialer Defaults]
Modem = /dev/noz0
Baud = 460800
SetVolume = 0
Dial Command = ATDT
FlowControl = NOFLOW
Init1 = ATZ
Init2 = ATM0
Auto DNS = 1

[Dialer iplus]
Username = any
Password = any
Phone = *99***1#
Stupid Mode = 1
Init3 = AT+CGDCONT=1,"IP","www.plusgsm.pl"
Dial Attempts = 3

W przypadku gdy modem jest pod innym urządzeniem, trzeba odpowiednio zmienić /dev/noz0

Następnie trzeba edytować, lub stworzyć jeżeli nie istnieje, plik /etc/ppp/peers/wvdial:

noauth
debug
/dev/noz0
115200
defaultroute
crtscts
lock
local
nodetach
usepeerdns
lcp-echo-failure 4
lcp-echo-interval 65535

Uruchomienie

Najpierw rejestrujemy się w sieci:

# comgt -d /dev/noz0

Enter PIN number: ****
Waiting for Registration..(120 sec max).
Registered on Home network: "Plus",2
Signal Quality: 18,99

Ostatnim krokiem jest połączenie z siecią

# wvdial iplus

Debugowanie Django w Winpdb

Django posiada wbudowaną stronę służącą do zgłaszania błędów, dzięki której można zidentyfikować źródło błędu. Pojawia się zawsze gdy wystąpi nieobsłużony wyjątek, pod warunkiem, że w ustawieniach opcja DEBUG przyjmuje wartość True. Każdy developer Django pewnie widział ją nie raz. Jednak błędy oprogramowania, to nie tylko wyjątki, ale także niepoprawnie wyświetlane dane - tego Django już nie wyłapie. Czasami też zdaża się, że informacja ze strony błędu, to za mało, żeby zidentyfikować błąd. Można wtedy posiłkować się funkcją print, której zwróconą wartość widać w konsoli, na której został uruchomiony serwer developerski. Jest to jednak przydatne przy naprawie drobnych błędów, gdzie szybko można zorientować się co może być ich przyczyną. Korzystanie z print na pewno nie jest debugowaniem z prawdziwego zdarzenia. Żeby dokładnie prześledzić działanie stworzonego skryptu, najlepiej posłużyć się narzędziem przeznaczonym do tego. Takim narzędziem jest Winpdb - graficzna wersja pdb (Python Debugger).

Debugowanie aplikacji stworzonych w Django jest prostsze, niż mogłoby się to wydawać. Do prezentacji posłużyłem się bardzo prostym, testowym projektem:

test_project/
|-- __init__.py
|-- manage.py
|-- settings.py
|-- test_app
|   |-- __init__.py
|   |-- models.py
|   `-- views.py
`-- urls.py

Kod który chcę prześledzić w poszukiwaniu błędów znajduje się w pliku test_app/views.py

def test_view(request):
        a = 1
        b = 3
        c = a + b

        print c

Pierwszym krokiem do prześledzenia skryptu, jest uruchomienie developerskiego serwera Django z poziomu Winpdb. Najpierw wybieramy "File -> Launch" W oknie które się pojawi,trzeba wpisać polecenie, które uruchomi serwer developerski.

Launch command

W moim przypadku było to:

/home/jaro/strony/test_project/manage.py runserver --noreload

Opcja noreload umożliwia debugowanie i zatrzymywanie się wykonywania skryptu w breakpointach. Skrypt zostaje załadowany i żeby go uruchomić trzeba kliknąć na ikonkę play. Spowoduje to uruchomienie serwera developerskiego Django. W konsoli wyświetli się komunikat serwera.

Konsola Winpdb

Do zatrzymania wykonywania skryptu w wybranym miejscu, można posłużyć się breakpointem. W pierwszej kolejności trzeba otworzyć plik, w którym breakpoint będzie ustawiony (File -> Open Source). Plik załaduje się do okna "Source".

Breakpoint

Breakpoint ustawia się poprzez kliknięcie na linię kodu, która z kolei zostanie wyróżniona czerwonym kolorem, tak jak jest to widoczne na powyższym rysunku.

Już wszystko jest gotowe do debugowania, teraz wystarczy wejść w przeglądarce na stronę, która uruchomi interesujący nas widok (np. http://localhost:8000/). Strona nie załaduje się, a jak przejdziemy do debuggera, to zobaczymy skrypt zatrzymany we wskazanej wcześniej linii.

Debugowanie skryptu

W widocznych oknach można zobaczyć aktualne wartości zmiennych, uruchomione wątki, stos, konsolę oraz kod źródłowy, którego kolejne linie można wykonywać interaktywnie, korzystając z przycisków w pasku narzędzi.

Internet przez komórkę pod Linuxem

Zazwyczaj jak korzystam z Internetu na laptopie, to albo w domu, albo gdzieś gdzie jest sieć bezprzewodowa. Czasami jednak pojawiają się sytuacje, w których potrzebuję dostępu do Internetu w miejscach, w których nie łapię żadnej sieci bezprzewodowej i nie ma gdzie się wpiąć kablem. Z pomocą przychodzi GPRS.

Co jest potrzebne żeby uruchomić GPRS pod Linuxem:

  1. telefon, który obsługuje tę technologię komunikacyjną
  2. włączoną opcję połączeń z Internetem przez kabel USB (w SE k800i ustawienia->łączność->USB->Włącz)
  3. kabel do telefonu lub blutetooth (przez IrDA chyba też się da)
  4. skompilowany i załadowany moduł jądra do obsługi modemów przez USB (USB_ACM)
  5. program global 3g

Modem w jajku

Po podłączeniu telefonu do komputera przez kabel, w katalogu /dev, powinny znajdować się pliki ACM*. Jeżeli są, to znaczy że moduł USB_ACM jest załadowany. W przeciwnym wypadku, najpierw można spróbować załadować go ręcznie (być może z jakiegoś powodu nie powiodło się automatyczne ładowanie):

# modprobe cdc_acm

Jeżeli to nie pomogło, to trzeba skompilować moduł USB_ACM. Nie będę opisywał szczegółowo jak to się robi, bo to temat na osobny wpis. W Internecie jest dużo informacji o kompilacji jądra. Podpowiem tylko gdzie i jakiej opcji w menu konfiguracyjnym trzeba szukać:

Location:
    -> Device Drivers
      -> USB Support
        -> Support for Host-side USB 
          -> USB Modem (CDC ACM) support

Po skompilowaniu modułu, powinien on automatycznie ładować się gdy telefon zostanie podłączony do komputera.

Połączenie ze światem

Czas na punkt kulminacyjny. Do połączenia użyłem świetnego programu Global 3G Jego wielkimi zaletami są wbudowane ustawienia do najpopularniejszych operatorów sieci komórkowych (w tym także polskich) oraz sterowniki do wielu telefonów. W konfiguracji programu wybrałem swojego operatora z listy dostawców, natomiast z listy urządzeń odpowiedni telefon. Posiadam Sony Ericsson k800i, który widnieje na liście obsługiwanych urządzeń, ale jak go wybrałem, to nie łączył się z Internetem. Dopiero jak wybrałem opcję "Sony Ericsson - Standard Models (2)", to zadziałało. W przypadku połączenia telefonu przez kabel, jako port trzeba wybrać USB -> ACM0 lub ACM1 jeżeli nie zadziała. Piszę tylko o kablu, bo nie próbowałem łączyć się przez blutetooth.

To by było na tyle jeśli chodzi o konfigurację. Wystarczy kliknąć "połącz" i wrota Internetu otworzą się przed nami. Wolnego, bo wolnego, ale w sytuacji awaryjnej wystarczy. Polecam wyłączyć w przeglądarce pobieranie obrazków, animacji, javascriptu i apletów javy. Niestety jest jeden szkopuł. Global 3G w darmowej wersji przed nawiązaniem połączenia z Internetem, zmusza nas do odczekania 6 minut (tylko za pierwszym razem w ciągu pojedynczego uruchomienia programu) i nie pamięta konfiguracji. Jeżeli chcemy pozbyć się tej uciążliwości, to trzeba zakupić od autora płatną wersję za 29 zł. Wydaje mi się, ze nie są to duże pieniądze i czuję, że fajnie jest zapłacić komuś drobną kwotę za dobrze wykonaną robotę.

Mobilność jest ekstra

Ostatnio przedłużyłem umowę z Orange i dzięki temu za jedyne 19 zł zakupiłem Sony Ericssona k800i. Wcześniej posiadałem (podobno kultowego) NEC-a e616v, który jest już trochę stary, ale cenię go za łatwość obsługi i solidność wykonania. Niemniej jednak miał trochę ograniczeń, typu mało funkcjonalny bluetooth, który służył tylko do obsługi bezprzewodowej słuchawki, czy MIDP niby w wersji 2.0, ale musiałem sporo nakombinować się, żeby zainstalować na nim jakieś aplikacje. Jak już jakąś udało się zainstalować, to zazwyczaj w wersji okrojonej.

Przy wyborze telefonu istotna dla mnie była obsługa protokołu SyncML, ponieważ potrzebuję możliwości synchronizacji Kalendarza Google z kalendarzem w telefonie. Oprócz tego dzięki temu protokołowi można eksportować na serwer kontakty, zadania i notatki. Ogólnie przydatne przy zmianie telefonu, albo po prostu jako backup.

Aplikacje które zainstalowałem

  • Fotokody - program do obsługi fotokodów
  • Gmail - klient poczty Gmail
  • Google Maps - z ciekawszych możliwości, to pokazywanie aktualnej lokalizacji, nawet jeżeli telefon nie posiada GPS-a
  • MidpSSH - klient SSH i Telnet
  • Opera Mini - mobilna wersja świetnej przeglądarki internetowej. Z powodzeniem zastępuje wbudowaną w telefon przeglądarkę stron WWW
  • Talkonaut - aplikacja umożliwia dzwonienie przez VoIP, korzystając z EDGE, 3G, WIFI. Ja używam go jako klienta Jabbera.
  • Wordmax - mobilny słownik Polsko-Angielski i Angielsko-Polski

Synchronizacja z Google Calendar

Niestety sam Google Calendar jeszcze nie umożliwia synchronizacji z innymi urządzeniami niż BlackBerry, iPhone i telefony z Windows Mobile. Musiałem więc szukać innych rozwiązań. Najpierw natknąłem się na GooSync. Początkowo wszystko działało jak należy, ale po około tygoniu postanowiłem szukać czegoś innego, bo, krótko mówiąc, przestało działać. Wybór padł na Schedule World, który oprócz kalendarza, umożliwia synchronizację zadań, notatek i książki adresowej. Możliwa jest także synchronizacja kontaktów z Gmaila. Życie stało się łatwiejsze, odkąd swój kalendarz mam w kieszeni i mogę dodawać wydarzenia zarówno przy pomocy komputera jak i telefonu.

Podsumowując, mobilność jest ekstra - mogę szybko zorientować się gdzie jestem przy pomocy Google Maps, sprawdzić pocztę wszędzie gdzie jest zasięg GPRS, sprawdzać rozkłady jazdy autobusów i pociągów przy pomocy Opery Mini. Nasuwa się jedno pytanie - ile to kosztuje? To zależy od operatora. Orange pobiera 25 groszy za 50 kB pobranych danych. Gdy z internetu korzysta się często, warto wykupić pakiet 5 MB za 5 zł. 5 MB w zupełności wystarczy do synchronizacji kalendarza i przeglądania kilku stron od czasu do czasu.

Wysyłanie maila z konsoli - skrypt Pythona

Jakiś czas temu postawiłem subversion na serwerze developerskim, który służył do testowania aplikacji którą piszę. Chciałem żeby informacje o każdym commit-cie były wysyłane na maila. Postanowiłem zrobić to przy pomocy skryptu w Pythonie.

import smtplib
import getopt
import email.Message
import sys

def main(argv):
    opts, args = getopt.getopt(argv, "u:p:s:h:t:r:t:", ["server=", "port=", "sender=", password=", "to=", "subject=", "text="])
    for opt, arg in opts:
        if opt in ["-u", "--server"]:
            serverURL = arg
        elif opt in ["-p", "--port"]:
            port = arg
        elif opt in ["-s", "--sender"]:
            sender = arg
        elif opt in ["-h", "--password"]:
            password = arg
        elif opt in ["-t", "--to"]:
            to = arg
        elif opt in ["-r", "--subject"]:
            subject = arg
        elif opt in ["-t", "--text"]:
            text = arg

    message = email.Message.Message()
    message["To"]      = to
    message["From"]    = sender
    message["Subject"] = subject
    message.set_payload(text)
    if port:
        mailServer = smtplib.SMTP(serverURL, port)
    else:
        mailServer = smtplib.SMTP(serverURL)
    mailServer.ehlo()
    mailServer.starttls()
    mailServer.ehlo()
    mailServer.login(sender, password)
    
    mailServer.sendmail(sender, to, message.as_string())
    mailServer.quit()

Wiadomość ma wysyłać się z serwera SMTP na gmailu, więc istotna jest tu część kodu:

mailServer.ehlo()
mailServer.starttls()
mailServer.ehlo()

Żeby wysłać maila, wystarczy wpisać polecenie: python /srv/mail.py --server=smtp.gmail.com --port=587 --sender=<adres_nadawcy> --password=<hasło_nadawcy> --to=<adres_odbiorcy> --subject="temat" --text="tresc wiadomosci"

Rozwijanie nazw w standardowej konsoli pythona

Konsola Pythona najczęściej służy mi do testowania drobnych fragmentów kodu. Zdarzało się że zapominałem jak nazywa się jakaś metoda danej klasy, albo chciałem szybko zobaczyć, jakie metody dana klasa udostępnia. Odkąd włączyłem rozwijanie nazw, które działa Unixopodobnie (po naciśnięciu klawisza Tab), nie stanowi to żadnego problemu. Sprawa jest bardzo prosta. Wystarczy w katalogu domowym utworzyć plik .pythonrc:

try:
    import readline
except ImportError:
    print "Brak modułu readline"
else:
    import rlcompleter
    readline.parse_and_bind("tab: complete")

Na koniec należy ustawić zmienną środowiskową:

export PYTHONSTARTUP=~/.pythonrc

Żeby nie robić tego za każdym razem po ponownym uruchomieniu systemu, wystarczy dopisać powyższą linijkę kodu do pliku .bashrc znajdującego się w katalogu domowym.

Zenity - bash i okna GTK

Jakiś czas temu przy aktualizacji paru pakietów, zwróciłem, nie wiem czemu, uwagę na jeden z nich. Jego nazwa nic mi nie mówiła, przeczytałem więc krótki opis, który zachęcił mnie do "googlowania". Mowa o Zenity, umożliwiającym uruchamianie zdefiniowanych okienek GTK o określonej funkcjonalności, z przeznaczeniem do wykorzystania w skryptach bash-a. Nie będę opisywał dostępnych okienek, bo to można wyczytać z dokumentacji. Skupię się zatem na konkretnym przykładzie wykorzystania Zenity.

Na szybko napisałem skrypt, który zastąpił moją metodę pomniejszania obrazków z wykorzystaniem z linii poleceń, opisaną w jednym z wcześniejszych wpisów.

#!/bin/bash

files=(`zenity --file-selection --title "Wybierz pliki do zmniejszenia" --multiple --separator " "`)

if [ $files == "" ]
then
        exit
fi

fnum=${#files[@]}
percent=$[100/$fnum]
current=$percent

scale=(`zenity --scale --title "Rozmiar miniatury" --text "Wybierz maksymalny rozmiar wysokości lub szerokości - obraz będzie zmniejszony proporcjonalnie" --max-value 1024`)

if [ $scale == 0 ]
then
        exit
fi

(for file in ${files[*]};
do
        type=`file -b -i $file`
        if [ ${type:0:5} == 'image' ]
        then            
                convert $file -resize "$scale"x"$scale" $file
        fi
        
        current=$[$current+$percent]
        echo $current
        
done) | zenity --progress --title "Zmniejszanie obrazków" --text "Postęp" --auto-close

Jest bardzo prosty, ale mi w zupełności wystarcza. Wykorzystuje polecenie "convert" z pakietu ImageMagick.

Debian & Broadcom Wireless & ndiswrapper

Ostatnio zakupiłem sobie router TP-Link TL-WR542G. Wcześniej używałem starego komputera z Pentium III 500 MHz, 96 RAM, z Gentoo w środku. Fajnie jest mieć router, którym można sobie zarządzać z konsoli, ale ma kilka wad - głośno chodzi, brak wi-fi, większy pobór prądu, zajmuje więcej miejsca. Ale starczy narzekania, do rzeczy. Pierwsze uruchomienie, szybka konfiguracja i coś tu nie gra. Internet strasznie zamula przez wi-fi. Wyniki testu łącza strasznie słabe - 300 kb przy pobieraniu danych, podczas gdy przy połączeniu przez kabel wychodziło około 2Mb. Jak nie trudno domyślić się, przyczyna tkwiła w sterowniku (BCM43xx). Z pomocą przychodzi ndiswrapper, który umożliwia instalację sterowników kart sieciowych, napisanych dla Windowsa. Sterowniki oraz listę obsługiwanych kart można znaleźć na stronie projektu. Przy wyborze sterownika należy zwrócić uwagę na pciid karty, które można odczytać przy pomocy polecenia lspci -nn. W moim przypadku jest to 14e4:4320
$ lspci -nn | grep Broadcom
02:04.0 Network controller [0280]: Broadcom Corporation BCM4306 802.11b/g Wireless LAN Controller [14e4:4320] (rev 03)
więc dla mojej karty odpowiedni jest sterownik bcmwl5. Czas na istalację: # aptitude install ndiswrapper-common
# ndiswrapper -i bcmwl5.inf
Jeszcze tylko załadowanie modułu: # modprobe ndiswrapper
FATAL: Module ndiswrapper not found
Jak widać, moduł który chcę załadować nie istnieje. Trzeba więc taki moduł stworzyć. Potrzebne do tego będą pakiety: build-essential, module-assistant, ndiswrapper-source oraz linux-headers-2.[wersja_jądra], w przypadku korzystania z jądra z repozytorium. # aptitude build-essential module-assistant ndiswrapper-source
# m-a a-i ndiswrapper
# modprobe ndiswrapper
Teraz już można w pełni cieszyć się bezprzewodowym dostępem do internetu.
Żeby moduł uruchamiał się przy starcie systemu, wystarczy dopisać go do listy /etc/modules: # echo "ndiswrapper" >> /etc/modules i dodać moduł bcm43xx do czarnej listy, żeby nie ładował się przy starcie systemu i nie kolidował z ndiswrapperem: # echo "blacklist bcm43xx" >> /etc/modprobe.d/blacklist

Phishing Paypal

Dostałem dziś takiego oto maila:

Jeżeli masz konto na PayPal i dostałeś takiego maila, to nie daj się zwieść! To jest przykład klasycznego phishing-u, czyli po krótce podszywania się pod oryginalną stronę. Kliknięcie na link w mailu i logowanie się na stronę która się wyświetli spowoduje przesłanie danych dostępowych do konta osobom niepowołanym.

Po czym poznać że to oszustwo? Akurat nie mam konta na PayPal, więc było mi łatwiej, ale można zauważyć tu kilka podejrzanych rzeczy. Najbardziej rzuca się w oczy adres nadawcy. Zamiast domeny paypal.co.uk, jest domena paypai.co.uk. Nieuważni użytikownicy przeoczą to i nie zauważą, że zamiast "l", jest małe "i". Oprócz tego podejrzana jest treść maila - zawieszenie konta z powodów bezpieczeństwa? I prośba o zalogowanie się w celu weryfikacji właściciela konta... ewidentnie coś tu śmierdzi.

Bądźcie czujni!

Tworzenie miniaturek z linii poleceń

Już wspominałem o tym w moim starym blogu, ale skoro ten blog ma zawierać głównie wpisy o tematyce technicznej, postanowiłem go umieścić również tutaj.

Czasami trzeba szybko zmniejszyć dużą ilość zdjęć, żeby np. umieścić je w galerii internetowej. Pod Linuxem jest na to prosty sposób. Wszystko czego potrzebujemy to pakiet ImageMagick.
Zakładam że będą pomniejszane wszystkie zdjęcia w formacie jpg z bieżącego katalogu.

W linii poleceń wystarczy wpisać: find . -maxdepth 1 -name '*.jpg' -exec convert {} -resize 800x800 {} ';' No i co to robi? Polecenie find wyszukuje w bieżącym katalogu wszystkie pliki o rozszerzeniu .jpg i wykonuje polecenie convert (z pakietu ImageMagick). Nawiasy "{}" oznaczają nazwę znalezionego pliku. Powyższe polecenie nadpisze oryginalne zdjęcia zmniejszonymi. Jeżeli chcemy uniknąć nadpisania, to można to zrobić np. tak find . -maxdepth 1 -name '*.jpg' -exec convert {} -resize 800x800 'min/{}' ';' Spowoduje to zapisanie miniaturek do katalogu min, który jest podkatalogiem bieżącego katalogu. Oczywiście "min" trzeba wcześniej utworzyć.
Jeżeli chcemy uniknąć nadpisania oryginalnych plików i zapisać miniaturki w bieżącym katalogu, można dodać prefiks przed nazwą miniatury: find * -maxdepth 1 -name '*.jpg' -exec convert {} -resize 800x800 "min"{} ';' Czemu akurat 800x800? Polecenie convert pomniejsza zdjęcia proporcjonalnie, więc jeżeli szerokość jest większa od wysokości to zmniejszy do rozdzielczości 800x..., a jeżeli jest odwrotnie to do rozdzielczości ...x800. Jeżeli nie chcemy zmniejszać wszystkich zdjęć do tego samego rozmiaru, tylko proporcjonalnie, to można użyć procentów, np. zamiast 800x800, 50%x50%. Opcja maxdepth określa ile poziomów podkatalogów ma być przeszukiwanych. Dla wartości 1 przeszukiwany jest tylko bieżący katalog.

W zasadzie powyższe przykłady prezentują cząstkę możliwości zarówno polecenia convert jak i find. Używając tego drugiego w analogiczny sposób można robić wiele innych rzeczy, np. przekonwertować wiele plików wav na mp3 przy użyciu lame.
ImageMagick jest wyposażony w narzędzie mogrify, które może pomniejszyć wiele zdjęć wg podanego filtra, więc te nieco długie powyższe przykłady, można zapisać krócej: mogrify -resize 800x800 *.jpg Mogrify nadpisuje oryginały, więc jeżeli nie chcemy ich utracić, trzeba pracować na kopiach.

Nowy rozdział.

Postanowiłem zmobilizować się do regularnego pisania, mało tego, do pisania o sprawach technicznych, wyszukiwania czegoś ciekawego i przelewania na bloga swoich doświadczeń. Wierzę w to, że dzięki staraniom o jakość wpisów, moja wiedza na dany temat będzie chociaż trochę bardziej rzetelna.

Oddzielam więc tematykę techniczną od mojego pierwotnego joggera (aczkolwiek nie usuwam ostatecznie), na którym będę pisał na luźne tematy i tak o dla siebie, dla uzewnętrznienia swoich wnętrzności.

Let the force be with me.