wtorek, 26 luty 2008

Sound Blaster X-Fi Linux Driver vs. Draken - zwycięstwo :)

Czas na powrót w wielkim stylu po długiej, blisko dwumiesięcznej nieobecności :)

Post będzie z cyklu: "wracamy do tematu". ;)

Jak zapewne pamiętacie, kilka miesięcy temu, w poście pt: "Sound Blaster X-Fi Linux Driver vs. Draken 2:0" opisałem dwie nieudane próby uruchomienia ww. karty pod linuxem SUSE 10.3. Próby te dotyczyły drivera w wersji beta (tja, widziałem już bardziej "dorobione" wersje alfa, ale jak zwał tak zwał :)) autorstwa Creative. Driver ten, dla przypomnienia, został wypuszczony pod koniec września zeszłego roku i zebrał ok 99,9% negatywnych opinii od użytkowników. Od tego czasu Creative nabrał wody w usta.

No ale, na całe szczęście, środowisko nie próżnowało i pierwszego lutego br. OSS wypuścił wersję 4.0 b. 1013 swoich driverów, zamieszczając tam wersję "early BETA" drivera SB X-Fi. Nawet Creative Open Source, zamieścił o tym krótką notkę na swojej stronie.

Proces instalacji
Jak zazwyczaj, są dwa sposoby na instalację: paczka RPM (należy pamiętać o ściągnięciu właściwej) oraz kompilacja ze źródeł. Po wykonaniu instalacji trzeba jeszcze przeprowadzić niezbędną konfigurację

Instalacja paczki RPM
Instalujemy klasycznie za pomocą komendy:

rpm -i [nazwa_paczki].rpm
i tyle na ten temat.

Instalacja ze źródeł
Najpierw musimy ściągnąć paczkę ze źródłami, najlepiej stąd.
Później należy postępować zgodnie z zawartymi w paczce instrukcjami, dlatego że proces instalacji różni się znacznie od klasycznego configure.sh&&make&&make install. Szczególną uwagę należy poświęcić prerekwizytom. Ponadto, trzeba jeszcze doinstalować narzędzie libtool (na liście prerekwizytów śladu po nim nie ma, natomiast bez niego instalacja się wykłada).

Konfiguracja
Wszystko jedno, który sposób instalacji wybierzemy, zakończy się ona z wielkim hukiem, wyświetleniem błędu:
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
ERROR: Module snd_intel8x0 is in use
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
ERROR: Module snd_intel8x0 is in use
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
ERROR: Module snd_intel8x0 is in use
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
ERROR: Module snd_intel8x0 is in use
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
ERROR: Module snd_intel8x0 is in use
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
ERROR: Module snd_intel8x0 is in use
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
ERROR: Module snd_intel8x0 is in use
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
ERROR: Module snd_intel8x0 is in use
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
ERROR: Module snd_intel8x0 is in use
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
ERROR: Module snd_intel8x0 is in use
ERROR: Module snd_ac97_codec is in use by snd_intel8x0
ERROR: Module snd_pcm is in use by snd_intel8x0,snd_ac97_codec
ERROR: Module snd_timer is in use by snd_pcm
ERROR: Module snd_page_alloc is in use by snd_intel8x0,snd_pcm
Failed to disable conflicting sound drivers
Reboot and try running soundon again
Błąd ten jest spowodowany konfliktem driverów OSS z zainstalowanymi domyślnie w OpenSUSE 10.3 driverami ALSA. Jednakże OSS zawiera skrypt, za pomocą którego możemy zdezaktywować drivery ALSA. W tym celu należy wykonać następujące kroki:
1. Zalogować się jako root
2. Uruchomić skrypt /usr/lib/oss/scripts/remove_drv.sh
3. Po uruchomieniu tego skryptu należy skasować to co zostało zainstalowane (czyli rpm -e oss-linux jeżeli instalowaliśmy z paczki, lub make clean jeżeli ze źródeł)
4. i powtórzyć instalację. Teraz nie powinno być już konfliktu sterowników. (Bardziej treściwe omówienie tematu zawiera ten wątek forum OSS.)

Na sam koniec nie zawadzi przenieść naszą kartę dźwiękową na sam wierzch stosu wykrytych przez OSS urządzeń. Aby tego dokonać:
1. Logujemy się jako root
2. Edytujemy plik /usr/lib/oss/etc/installed_drivers, przenosząc wpis rozpoczynający się od sbxfi do linii 1

Na zakończenie restartujemy system.

Po restarcie, za pomocą komendy
osstest
testujemy instalację sterowników.

Z testów wynika, że driver pozwala karcie na działanie jedynie w trybie 2.1 (zamiast 7.1 jak pod Windows). No ale od czegoś trzeba przecież zacząć. :)

niedziela, 30 grudzień 2007

Czysta skatologia

Dzisiejszy odcinek poświęcony bedzie automatycznej analizie ryzyka wystapienia antywzorców utrudniających zarządzanie zmianą w kodzie napisanym w Javie. Czyli w skrócie - Change Risk Anti-Patterns 4J. Jeszcze bardziej w skrócie - CRAP4J.

CRAP4J jest niewielkim programem integrującym się z Eclipsem lub Antem, potrafiącym przeanalizować i ocenić kod w Javie pod kątem prawdopodobieństwa wystapienia w nim błędów powstałych podczas nanoszenia zmian (tzw. regresji). Kod oceniany jest na podstawie dwóch kryteriów. Pierwszym jest stopień skomplikowania szacowany głównie na podstawie liczby punktów decyzyjnych zawartych w każdej metodzie (punktami decyzyjnymi są klauzule "if" oraz pętle). Drugie kryterium stanowi współczynnik (podawany w %) pokrycia kodu testami jednostkowymi. Po przeanalizowaniu kodu, CRAP4J opracowuje zebrane dane w formie graficznej (p. rys.)



Dane te zawierają porównany ze średnią światową procent metod w projekcie, które nie spełniaja kryteriów jakościowych (CRAPpy methods count) oraz szacunkową ilość pracy pozwalającej na doprowadzenie tych metod do porządku (współczynnik CRAP Load). Ponadto, na arkuszu wyników znaleźć można jeszcze histogram podający rozkład współczynnika CRAP w metodach naszego kodu.

Co należy zrobić aby obniżyć współczynnik CRAP w kodzie? Zasadniczo, dwie rzeczy:
1) można pisać mniej skomplikowane (krótsze, zawierające mniejszą liczbę punktów decyzyjnych) metody.
2) należy zwiększyć pokrycie metod przez testy jednostkowe.

Reasumując, CRAP4J to nie "silver bullet", który sprawi, że regularne przeglądy kodu staną się zbędne; narzędzie to potrafi jednak dać kilka istotnych wskazówek ułatwiających refactoring niektórych zbyt skomplikowanych metod.

piątek, 14 grudzień 2007

Fun with XML Beans

Po tym jak po raz dziesiąty usłyszałem pytanie "a co to są te XML beans?", o których wspomniałem w jednym z poprzednich postów, stwierdziłem że najwyższy czas żeby szanownym Czytelnikom tego bloga temat nieco przybliżyć.

W telegraficznym skrócie, Apache XMLBeans to biblioteka służąca do konwersji plików XML do grafu klas Javy. Operacja odwrotna (czyli zapis grafu klas do pliku XML) również jest możliwa.

Skąd XMLBeans wie w jaki sposób dany plik XML powinien zostać skonwertowany na klasę javy? Mówi mu o tym plik definiujący schemat XML (XML Schema Definition, .xsd). Możemy go wygenerować za pomocą dostarczanego z XMLBeans narzędzia inst2xsd. Narzędzie to przyjmuje Na wejście najbardziej typowy dokument XML, dla którego chcemy taki schemat stworzyć. Skorzystajmy z przykładu z poprzedniego posta:

<?xml version="1.0" encoding="UTF-8"?>
<person>
<name>Jan</name>
<surname>Kowalski</surname>
<birthday>19800425</birthday>
<salutation>Mr</salutation>
</person>

Podaną wyżej treść XML zapisujemy do pliku o nazwie person.xml i wydajemy polecenie:
%XMLBEANS_HOME%\bin\inst2xsd -outPrefix person person.xml

W wyniku otrzymujemy plik person0.xsd zawierający schemat. Zawartość pliku znajduje się poniżej:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="person" type="personType"/>
<xs:complexType name="personType">
<xs:sequence>
<xs:element type="xs:string" name="name"/>
<xs:element type="xs:string" name="surname"/>
<xs:element type="xs:int" name="birthday"/>
<xs:element type="xs:string" name="salutation"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

Następnie należy skompilować plik schematu. Robimy to za pomocą narzędzia scomp wydając polecenie:
%XMLBEANS_HOME%\bin\scomp -out person.jar person0.xsd

Wynikiem będzie plik person.jar zawierajacy skompilowane klasy reprezentujące dokument XML.
Mając taki plik możemy teraz:

1) czytać pliki XML zgodne ze schematem i posługiwać się nimi tak jak zwykłymi klasami Javy

Aby wczytać przykładowy plik XML, możemy skorzystać z poniższego przykładu:
public static void getPerson() throws Exception {
File personXML = new File("person.xml");
PersonDocument personDoc = PersonDocument.Factory.parse(personXML);

PersonType person = personDoc.getPerson();
person.getName();
person.getSurname();
person.getBirthday();
person.getSalutation();
}

2) tworzyć zgodne ze schematem pliki XML, wypełniając je dowolną treścią

Aby wygenerować zgodny ze schematem plik XML, należy skorzystać z poniższego kodu. Kod ten zawiera również fragment sprawdzający poprawność generowanego dokumentu XML.
public static void setPerson() throws Exception {
PersonDocument personDoc = PersonDocument.Factory.newInstance();

PersonType person = PersonType.Factory.newInstance();
person.setName("Jan");
person.setSurname("Kowalski");
person.setBirthday(19800425);
person.setSalutation("Mr");

personDoc.setPerson(person);

//Walidacja dokumentu
ArrayList validationErrors = new ArrayList();
XmlOptions validationOptions = new XmlOptions();
validationOptions.setErrorListener(validationErrors);
if(!personDoc.validate(validationOptions)) {
Iterator iter = validationErrors.iterator();
while (iter.hasNext()) {
System.out.println(">> " + iter.next() + "\n");
}
throw new Exception("Invalid data, cannot format XML file. See above for possible reasons");
}

System.out.println(personDoc.toString());
}

Wynikiem będzie plik:
<person>
<name>Jan</name>>
<surname>Kowalski</surname>
<birthday>19800425</birthday>
<salutation>Mr</salutation>
</person>

Oczywiście, na tym nie kończą się możliwości biblioteki. XMLBeans pozwala nam także na przeglądanie dokumentów XML, przeszukiwanie ich za pomocą XQuery/XPath (po integracji z biblioteką Saxon), podmianę treść XMLi "w locie" i na wiele, wiele więcej. Po szczegóły odsyłam do dokumentacji.

środa, 5 grudzień 2007

Oswajanie Kerberosa

Pozwolę sobie bezczelnie zalinkować tutaj opublikowany przeze mnie artykuł dotyczący integracji serwera BEA WebLogic z systemem Kerberos.

piątek, 23 listopad 2007

Joy of SAX

Dzisiaj będzie parę słów o chyba najbardziej znanym parserze XML - SAX Parser. A raczej o tym, jak ustrzec się błędu, który popełniany jest bardzo często przez programistów mających styczność z tym parserem po raz pierwszy w życiu (przyznam się bez bicia,że dawno temu sam zaliczyłem bardzo podobną wpadkę). Ale do rzeczy. Załóżmy, że na wejście programu dostaliśmy następującą treść XML:

<?xml version="1.0" encoding="UTF-8"?>
<person>
<name>Jan</name>
<surname>Kowalski</surname>
<birthday>19800425</birthday>
<salutation>Mr</salutation>
</person>

i mamy przekształcić ją na obiekt klasy
public class Person {
private String name;
private String surname;
private Date bithday;
private String salutation;

//insertery i ekstraktory pominąłem
}

Ok, można do tego użyć wyspecjalizowanych bibliotek, takich jak JAXB lub
XMLBeans, ale w wypadku, gdy mamy przeprowadzić jedynie tak prosty parsing, nazwałbym to przestrzeleniem problemu. W takim przypadku SAX najlepiej się sprawdza.

Wróćmy do naszego początkującego programisty. W jaki sposób pisze on klasę wykonującą parsing? Zazwyczaj tak:
public class PersonHandler extends DefaultHandler {

public static final String TAG_PERSON = "person";
public static final String TAG_NAME = "name";
public static final String TAG_SURNAME = "surname";
public static final String TAG_BIRTHDAY = "birthday";
public static final String TAG_SALUTATION = "salutation";

private String currentTagName;
private Person currentPerson;

public Person processPerson(InputStream xmlFileInputStream) throws IOException {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
saxParser.parse(xmlFileInputStream, this);
return currentPerson;
} catch (Exception e) {
throw new IOException("Error while parsing XML file, error="
+ e.printStackTrace());
}
}

/*
* Wywoływana po odczytaniu znacznika początkowego XML
*/
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
currentTagName = qName;
if (TAG_PERSON.equalsIgnoreCase(qName)) {
currentPerson = new Person();
}
}

/*
* Metoda pobierająca zawartość tekstową znacznika
*/
public void characters(char buf[], int offset, int len) throws SAXException {
String tagContent = new String(buf, offset, len);
if (tagContent != null && tagContent.trim().length() > 0) {
tagContent = tagContent.trim();
} else {
//wartość pusta, nic nie ustawiamy
return;
}
try {
if (TAG_NAME.equalsIgnoreCase(currentTagName)) {
currentPerson.setName(tagContent);
} else if (TAG_SURNAME.equalsIgnoreCase(currentTagName)) {
currentPerson.setSurname(tagContent);
} else if (TAG_BIRTHDAY.equalsIgnoreCase(currentElementName)) {
currentPerson.setBirthday(DateUtil.parseDate(tagContent, "yyyyMMdd"));
} else if (TAG_SALUTATION.equalsIgnoreCase(currentTagName)) {
currentPerson.setSalutation(tagContent);
}
} catch (ParseException e) {
log.warn("Could not obtain " + currentTagName + " tag value", e);
}
}

/*
* Wywoływana po odczytaniu znacznika koncowego XML
*/
public void endElement(String uri, String localName, String qName) throws SAXException {
//zamkniecie znacznika nie wiaze sie z jakakolwiek akcja
}

}

Awww..... Isn't that cute? But it's WRONG!!! Dlaczego? Dlatego, że metoda characters z poprzedniego listingu nie gwarantuje nam, że cała zawartość tekstowa znacznika zostanie wczytana za pojedynczym jej wywołaniem. (Jedyne co jest gwarantowane, to wywoływanie metody characters tak długo, dopóki zawartość znacznika nie zostanie odczytana). Dlatego może to prowadzić do sytuacji, w której program z powyższego listingu odczyta jedynie końcówki treści tekstowej znacznika (np.: imię: an, nazwisko: alski itd.), lub co gorsza, wysypie się podczas próby parsingu niekompletnej daty urodzin. Można to poprawić zapisując informacje do obiektu klasy Person() dopiero w momencie, gdy jesteśmy pewni, że dany znacznik już się zakończył - czyli podczas wykonywania metody endElement.
public class PersonHandler extends DefaultHandler {

public static final String TAG_PERSON = "person";
public static final String TAG_NAME = "name";
public static final String TAG_SURNAME = "surname";
public static final String TAG_BIRTHDAY = "birthday";
public static final String TAG_SALUTATION = "salutation";

private Person currentPerson;
private StringBuffer currentTagContent;

public Person processPerson(InputStream xmlFileInputStream) throws IOException {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
saxParser.parse(xmlFileInputStream, this);
return currentPerson;
} catch (Exception e) {
throw new IOException("Error while parsing XML file, error="
+ e.printStackTrace());
}
}

/*
* Wywoływana po odczytaniu znacznika początkowego XML
*/
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (TAG_PERSON.equalsIgnoreCase(qName)) {
currentPerson = new Person();
}
currentTagContent = new StringBuffer();
}

/*
* Metoda pobierająca zawartość tekstową znacznika
*/
public void characters(char buf[], int offset, int len) throws SAXException {
String partialTagContent = new String(buf, offset, len);
currentTagContent.append(partialTagContent);
}

/*
* Wywoływana po odczytaniu znacznika koncowego XML
*/
public void endElement(String uri, String localName, String qName) throws SAXException {
String currentTagStringContent;

if (currentTagContent.toString() != null && currentTagContent.toString().trim().length() > 0) {
currentTagStringContent = currentTagContent.toString().trim();
} else {
//wartość pusta, nic nie ustawiamy
return;
}

if (TAG_NAME.equalsIgnoreCase(qName)) {
currentPerson.setName(currentTagStringContent);
} else if (TAG_SURNAME.equalsIgnoreCase(qName)) {
currentPerson.setSurname(currentTagStringContent);
} else if (TAG_BIRTHDAY.equalsIgnoreCase(qName)) {
currentPerson.setBirthday(DateUtil.parseDate(currentTagStringContent, "yyyyMMdd"));
} else if (TAG_SALUTATION.equalsIgnoreCase(qName)) {
currentPerson.setSalutation(currentTagStringContent);
}
}
}

Update: dyskusję zachowania powyższego rozwiązania w środowisku wielowątkowym pozostawiam Czytelnikom.

czwartek, 15 listopad 2007

Sound Blaster X-Fi Linux Driver vs. Draken 2:0

Pod koniec września br. firma Creative Labs wypuściła wreszcie z utęsknieniem wyczekiwaną wersję beta drivera linuksowego do karty dźwiękowej Sound Blaster X-Fi. Piszę "z utęsknieniem" ponieważ od premiery tej karty do dzisiaj upłynęły już ponad dwa lata i przez ten czas szczęśliwi jej posiadacze-linuksiarze mogli w swoim ulubionym systemie operacyjnym rozkoszować się jedynie 24-kanałową, 192 kHz-ową... ciszą.

Oczywiście, gdy tylko dotarła do mnie wiadomość, że beta już jest, poszedłem na strony Creative Open Source żeby wreszcie móc posłuchać pod linuksem czegoś lepiej brzmiącego niż popiskiwania karty wbudowanej w płytę główną.

Pierwsze podejście. Status: FAILED

Ściągnąłem driver (dostępna jest tylko wersja dla 64-bitowych linuksów, właściciele 32-bitowych maszyn muszą się niestety obejść smakiem) i przeczytałem instrukcję instalacji. Tutaj od razu kubeł zimnej wody. Driver należy kompilować za pomocą gcc w wersji 3.30 (zuza ze swoim preinstalowanym 4.2.1 może się schować :P), ponadto gcc musi być w identycznej wersji jak to, którym skompilowano kernel (sic!). Konia z rzędem temu, kto potrafiłby mi wskazać działającego linuksa z 64-bitowym kernelem, który został skompilowany tak przedhistoryczną wersją gcc! Installation aborted.

Drugie podejście. Status: FAILED

Na całe szczęście, dotarła do mnie informacja, że pewien biegły w C++ Niemiec wypuścił patcha, który miał "ukompatybilnić" driver z gcc 4.2.x. Spóbowałem - rezultat (po kiku godzinach wskazywania kompilatorowi, gdzie ma szukać różnych plików nagłówkowych itp.) jest taki, że proces kompilacji wykłada się losowo w różnych miejscach. Więc, jak widać, ten patch nie usuwa wszystkich problemów na linii kod - wersja kompilatora.

Jak na razie czekam na pełną wersję drivera. Thanks for nothing, Creative!

Update: jakiś maniak w końcu skompilował ten driver w Suse 10.3. Niestety, podczas ładowania moduł drivera regularnie zalicza segfault. :/

piątek, 2 listopad 2007

Obyśmy nie doczekali...

W USA coraz głośniej słychać o próbie przepchnięcia przez Kongres ustawy zezwalającej ISPs na różnicowanie jakości usług dostępu do sieci według ich własnego widzimisię. Idea polega na tym, że kto więcej danemu ISP zapłaci, ten dostanie od niego gwarancję, że to właśnie jego usługi będą miały najwyższy priorytet podczas przepływu przez część sieci należącej do tego ISP. I to bez oglądania się na wolę i preferencje użytkowników korzystających z usług danego ISP. Przykładowo: masz dostęp do Internetu za pośrednictwem providera X i chcesz skorzystać z Gmaila? Zapomnij więc o przyzwoitej szybkości, ponieważ provider X podpisał właśnie umowę z Microsoftem mówiącą o tym, że największy priorytet będzie miał Hotmail.

Problem zresztą jest wieloaspektowy, czasami "różnicowanie jakości" może oznaczać całkowite zablokowanie dostępu do pewnych usług. Oczywiście, o ile nie dopłacisz paru dolarów więcej swojemu ISP za udostepnienie interesujących Cię treści. (Czy ktoś jeszcze się dziwi, że ISPs już zacierają ręce, widząc te płynące z wielu stron strumienie gotówki?) Organizacje pilnujące wolności słowa w USA obawiają się, że pakiety udostępniające blogi, czy też inne formy publikacji WWW, będą należały do najdroższych. Wszystko po to aby wprowadzając pewien rodzaj cenzusu majątkowego ograniczyć głosy antykorporacyjnej krytyki w sieci.

Poniższy obrazek publikuję ku przestrodze. Jest to, na całe szczęście, fake. I miejmy nadzieję, że jeszcze długo takim pozostanie.