14
Oca

Ofiste sürüm telaşı var. Haftaya çıkacak olan 2011 için herkes harıl harıl çalışıyor. Benim işlerim( bir sonraki blog yazımda onlardan bahsedeceğim) bittiği için Fatih’in yanına gidip yapabileceğim bir şeyler var mı diye sorunca, bana bu hatadan bahsetti. Bu sabah iki saat süren güzel bir araştırmanını sonunda çözmüştük. Teker teker nasıl çözüldüğüne dair bir yazı yazdım yine. Başlayalım.

Hata nedir ?

Pardus 2011′de bazı ekran koruyucuları çalışmıyor. Tıkladığınızda siyah bir ekran çıkıyor. Yani çalışmıyor. Bununla ilgili bir hata kaydı girilmiş (Bug 15578 – Ekran koruyucuların bazıları çalışmıyor ). Aşağıdaki resimde görebileceğiniz gibi ilk bölümdeki “Değişen Çizgiler” adındaki ekran koruyucu siyah bir şekilde gözüküyor

Elimizde ne var ?

Hata kaydında hangi ekran koruyucuların çalışmadı bilgisi var. Bunun dışında ekran koruyucuları oluştusan xscreensaver paketçisi olan Fatih Aşıcı‘nın bir yorumu mevcut: “Bu ekran koruyucular xscreensaver paketinden çıkmamaya başlamış. Pakette yanlış bir şey olup olmadığına bakayım.”

Çözüm

Sistem ayarlarında “Ekran ve Monitör” kısmında “Ekran Koruyucu” ile ekran koruyucularını seçebilir ve değiştirebiliriz. Bazı ekran koruyucuların neden çalışmadığına bakmak için doğrudan sadece bu modülü çalıştırıp bakmak gerekiyor. Sistem ayarlarında her ayarlama ünitesi aslında birer bağımsız bir uygulama. Bunları

kcmshell4 --list

ile görebilirsiniz. Buraya göz atınca “screensaver” diye bir modül olduğunu göreceksiniz. Bunu konsoldan doğrudan

kcmshell4 screensaver

ile çalıştırabilirsiniz. Çalıştırınca da “Ekran Koruyucu” olarak söylediğim pencerenin aynısı çıkıyor. Şimdi bu ünitenin ismini öğrendiğimiz göre ,kaynak kodlarını araştırmaya başlayabiliriz. Bu paketler genellikle kdebase-workspace ve kdebase-runtime paketlerinden çıkıyor. Ben kdebase-workspace paketini açıp içinde “screensaver” ismini aratınca karşıma şunlar çıkıyor:

find -iname screensaver
./kcontrol/screensaver
./plasma/design/screensaver
./plasma/screensaver
./plasma/screensaver/containments/screensaver
./doc/kcontrol/screensaver
./krunner/screensaver

görüldüğü gibi screensaver ile ilgili birden fazla klasör var. Bizim sorunumuz sistem ayarlarında olduğu için “kcontrol” klasörüne girip orayı kurcalamaya başladım. Birden fazla dosya olsa da, genelikle bu KDE modüllerinin bir ana dosyası olur. Bunların isimlendirmesi de kcmMODULISMI.cpp ya da main.cpp şeklinde. Fakat screensaver ile ilgili bu şekilde bir dosya yoktu. Ama klasör içinde .cpp dosyaları zaten azdı ve isimlere bakarak scrnsave.cpp dosyasının doğru olacağını düşünerek açtım.

Arayüzü oluşturan kodlar ana sınıfta olacağından oraya göz atmaya başladım. Şimdi diyeceksiniz ki, KDE ya da QT bilmek gerekiyor mu kodu anlamak için? Kesinlikle Hayır! Eğer başka dillerde bir iki syntax biliyorsanız hemen hemen her kodu okuyabilirsiniz. Mesela bu ana sınıfta şöyle bir kod parçası var:

mSaverListView->setColumnCount(1);
mSaverListView->header()->hide();
mSelected = -1;
connect( mSaverListView, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(slotSetup()));
connect( mSetupBt, SIGNAL( clicked() ), SLOT( slotSetup() ) );
connect( mTestBt, SIGNAL( clicked() ), SLOT( slotTest() ) );
mEnabledCheckBox->setChecked(mEnabled);
connect(mEnabledCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotEnable(bool)));
mWaitEdit->setRange(1, INT_MAX);
mWaitEdit->setSuffix(ki18ncp("unit of time. minutes until the screensaver is triggered", " minute", " minutes"));

Bu koda mSaverListView bizim aramamız gereken kısım. Yani ekran koruyucuları oluşturan kısım muhtemelen. Burada muhtemelen diyorum çünkü ben şu an bunun ne yapacağını bilmiyorum. Ama isimden yola çıkarak ve arayüzde sadece bir liste olduğunu düşünürsek bu yolun yanlış olmayacağını düşünebiliriz.

Şimdi koda (scrnsave.cpp) de bu değişken ismini aratınca karşımıza birden fazla fonksiyonda karşımıza çıktığını göreceksiniz. Bunlara yine şöyle göz atınca bir iki ipucu alabiliyoruz.

void KScreenSaver::findSavers()
{
if ( !mNumLoaded ) {
    mSaverServices = KServiceTypeTrader::self()->query( "ScreenSaver");
    new QTreeWidgetItem ( mSaverListView, QStringList(), 18n("Loading...") );
if ( mSaverServices.isEmpty() )
   mLoadTimer->stop();
else
   mLoadTimer->start( 50 );
}
for( KService::List::const_iterator it = mSaverServices.constBegin();
it != mSaverServices.constEnd(); it++,mNumLoaded++)
{
SaverConfig *saver = new SaverConfig;
QString file = KStandardDirs::locate("services", (*it)->entryPath());
if (saver->read(file)) {
    mSaverList.append(saver);
} else
    delete saver;
}

Bakınız yine kodun ne yapacağını anlamamıza gerek yok. Önemli olan sorunun nereden kaynaklandığına bakmak. Mesela yazımın başında da belirtiğim gibi, xscreensaver’de bazı paketler eksik olduğunu söyledi Fatih Aşıcı.

Ben ise olaya biraz farklı baktım. Madem bu paketler eksik, neden listede biz bunları görüyoruz ? Listeyi oluşturan nedir ? Oluşturuyorsa neye göre oluşturuyor ?

Yukarıda koda bakınca şu satır göze çarpıyor:

QString file = KStandardDirs::locate("services", (*it)->entryPath());

file adında bir değişken oluşturuyor. Bunu da KDE’nin standart dizinlerinden biri olan “services” klasöründen oluşturuyor (bakınız kodun ne anlama geldiğine yine bakmadım, ama değişken isimleri zaten kendini anlatıyor)

Bundan sonra bu dizinlerin nerede olduğunu bakmaya karar verelim. Genelikle kde paketlerinin veri dosyaları /usr/share/kde4 altın atılır. Bu dizine girdiğimizde ise “services” diye bir klasör olduğunu görececeğiz. Hatta bu klasörün içinde “ScreenSavers” adında bir klasör mevcut ve klasörün içeriği de şu şekilde devam ediyor:

[/usr/share/kde4/services/ScreenSavers ]$ ls
abstractile.desktop   decayscreen.desktop     halo.desktop            loop.desktop          rubik.desktop
anemone.desktop       deco.desktop            helix.desktop           m6502.desktop         sballs.desktop
anemotaxis.desktop    deluxe.desktop          hopalong.desktop        maze.desktop          shadebobs.desktop
...

Onlarca desktop dosyası mevcut. Demek ki ekran koruyucu listesini bu dosyalar oluşturuyor. Daha doğrusu “Ekran Koruyucu” modülü listeyi buradaki dosyalardan oluşturuyor. Bu desktop dosyaların İçeriği ise şu şekilde:

[Desktop Entry]
Exec=xflame
Icon=kscreensaver
Type=Service
X-KDE-ServiceTypes=ScreenSaver
TryExec=xscreensaver
Actions=InWindow;Root;Setup;
X-KDE-Category=Gadgets;Simulations
...

Burada “Exec=” ile başlayan kısımlar ekran koruyucusunun uygulama isimlerini gösteriyor. Yani bu dosyalar çağırılıyor. “Ekran Koruyucu” modülünde, listede herhangi bir ekran koruyucusuna tıkladığınızda işte buradaki Exec komutları çalıştırılıyor. Exec komutlarında belirtilen ekran koruyucuları da xscreensaver paketi ile beraber geliyor. O zaman bu dizinin içeriği ile xscreensaver’den çıkan paketleri karşlılaştırınca. Bakalım neler göreceğiz.

Konsolda dünyanın en iyi metin düzenleyicisi olan Vim’i açıp (swh), “ex” modunda

:vs

yazarak iki tane temiz pencere açalım

Ardından xscreensaver’den çıkan çalıştırabilinir dosyaları doğrudan vimin ilk penceresine yapıştıralım. Vim’de bash komutlarını doğrudan çağırıp, çıktılarını yapıştırabilirseniz. “ex” modunda şu komutu çalıştırmak yeterli

:r !pisi info xscreensaver -F |grep libexec

İkinci pencereye geçip (Ctrl-W, W) , bu sefer /usr/share/kde4/services/ScreenSaver içeriğini yapıştıralım. “Ex” modunda şu komutu çalıştırdım bu sefer:

:r !ls /usr/share/kde4/services/ScreenSavers/

Güzel olan ise desktop dosyalarını isimleri ile xscreensaver’den çıkan ekran koruyucu uygulamalarının ismi aynı. Tek yapmamız gereken bu isimleri karşılaştırmak. Fakat resimde de gördüğünüz gibi ilk pencerede her satırın başında dizin yolun adresleri mevcut. ikinci pencerede de dosyalar .desktop dosyası ile başlıyor.

Hepsini tek tek değiştirmiyorum tabi :) Elimizde Vim gibi şahane bir uygulama var, neden kullanmayalım ? (Vim konusunda biraz fanatikliğim var, özür). Vim’de de macrolar mevcut. “qa” tuşu ile macroyu başlatıp, bir takım işler yapıp “q” ile kapatabilirsiniz. Sonra bu kaydedilen makroyu her satırda “@a” ile çağırabilirsiniz. “a” ise macronun içeriğini kaydeden belleğin ismi. İlk pencerede her satır için yapmamız gerekenler şunlar (vim komutularıyla beraber)

  1. Satırın sonuna gitmek. Vim komutu: “$
  2. Sonra bir kelime geriye gitmek. Vim komutu : “b
  3. Kelimenin başlangıcından sonuna kadar silmek. Vim komutu: “d0
  4. Bir alt satıra inmek – Vim komutu: “j

Evet üç tane hamle ile bunu güzelce halledebiliriz. Bunları sırayla yaptıktan sonra tekrar “q” tuşu ile macroyu kapatacağım. Tüm satırlar için ex modunda

:%norm! @a

çalıştırınca aşağıdaki sonucu elde ediyoruz:

İkinci pencere için ise yine bir macro oluşturuyoruz. Her satır için yapmamız gerekenler ise

  1. Satırın başına gelmek – Vim komutu: “0
  2. Satırda . ile başlayan konuma gelmek . Vim komutu: “f.
  3. Sonra bu konumdan satırın sonuna kadar silmek Vim komutu: “d$
  4. Bir alt satıra inmek – Vim komutu: “j

:%norm! @a çalıştırınca aşağıdaki sonucu elde ediyoruz:

Evet şimdi yan yana iki tane liste elde ettik. Bunları kaydedip vimdiff ile açabilirdik. Ki genelikle öyle yapılır. Fakat bu şekilde iki tane pencere açıkken de diff komutunu kullanabilrsiniz. Yapmamız gereken “ex” modunda şu satırı yazmak:

:windo diffthis

Sol tarafta xscreensaver uygulamaları görülüyor. Sağ tarafta ise /usr/share/kde4/services/ScreenSavers altındaki desktop dosyaları. Buradan görüldüğü gibi desktop dosyalarında fazlalıklar var (yeşil olanlar). Yani var olan desktop dosyaları var fakat ona karşılıklı gelen xscreensaver ekran koruyucuları mevcut değil. Hata’da buradan kaynaklanıyor.Bazıları çalışmıyorsa, sistemde kurulmadığı anlamına geliyor. Peki sistemde kurulu olmayan bir ekran koruyucu için neden bu dosyalar oluşuyor ? Bu dosyalar nerden geliyor o zaman ?

Pisi’nin bununla ilgili çok güzel bir komut var, rastgele bir dosya üzerinde bu komutu çalıştırınca:

pisi sf /usr/share/kde4/services/ScreenSavers/boxed.desktop
Searching for /usr/share/kde4/services/ScreenSavers/boxed.desktop
Package <strong>kdeartwork-screensavers</strong> has file /usr/share/kde4/services/ScreenSavers/boxed.desktop

Bu paketin hangi bileşene ait olduğunu öğrenmek için de pisi info çalıştırıyorum:

pisi info kdeartwork-screensavers
Installed package:
Name                : kdeartwork-screensavers, version: 4.5.5, release: 43
Summary             : Additional KDE4 screensavers
Description         : Contains additional screensavers for KDE4.
Component           : desktop.kde.base

Bileşenin desktop.kde.base olduğunu görünce svn deposunda o dizine girdim. Kdeartwork-screensavers adında bir klasör mevcut değildi fakat kdeartwork klasörü mevcut idi. Bu paketin pspec.xml dosyasına bakınca kdeartwork ile gelen bir alt paket olduğunu görünce paketi açıp devam ettim:

pisi bi pspec.xml --unpack

/var/pisi/ altında kaynak kodunun dizinine girdiğimizde hiç bir şey aratmadan kscreensaver klasörü gözüme çarptığı için oradan başladım devam etmeye. Bu sefer doğrudan .desktop aratarak, bu dosyaların neden oluştuğuna veyahut da nasıl oluştuğunu öğrenmek istedim. Arayınca şunlar çıktı:

grep -rHIin desktop .
./kxsconfig/ScreenSavers/truchet.desktop:1:[Desktop Entry]
./kxsconfig/ScreenSavers/decayscreen.desktop:1:[Desktop Entry]
./kxsconfig/ScreenSavers/photopile.desktop:1:[Desktop Entry]
./kxsconfig/ScreenSavers/jigglypuff.desktop:1:[Desktop Entry]
...

Şeklinde desktop dosyaları mevcuttu. Bunlar bizim desktop dosyalarının ta kendisi. Bu yüzden kxsconfig klasörüne girip oraları kurcalamaya başladım. Klasörün içindeki CMakelists.txt dosyası çözümün ta kendisiydi:

########### install files ###############

FILE(GLOB _desktopfiles "ScreenSavers/*.desktop" )
foreach(_currentdesktopfile ${_desktopfiles})
·   STRING(REGEX REPLACE ".desktop" "" _newitem "${_currentdesktopfile}" )
·   #MESSAGE(STATUS "newItem &lt;${_newitem}&gt;")
·   GET_FILENAME_COMPONENT(_screensaverName ${_newitem} NAME_WE)
·   MESSAGE(STATUS "name without extension &lt;${_screensaverName}&gt;")
·   find_file(XSCREENSAVER_FILE_FOUND ${_screensaverName}.xml PATHS ${XSCREENSAVER_CONFIG_DIR} )
·   if(XSCREENSAVER_FILE_FOUND)
·   ·   MESSAGE(STATUS "xscreensaver name ${_screensaverName} found")
·   ·   install(FILES ${_currentdesktopfile} DESTINATION  ${SERVICES_INSTALL_DIR}/ScreenSavers )
·   else(XSCREENSAVER_FILE_FOUND)
·   ·   MESSAGE(STATUS "xscreensaver name ${_screensaverName} not found")
·   endif(XSCREENSAVER_FILE_FOUND)
endforeach(_currentdesktopfile ${_desktopfiles})

Burası tam olarak düşündüğüm şeyi yapıyordu. Desktop dosyalarının ismini XSCREENSAVER_CONFIG_DIR altından bakıyor ve eğer aynı dosya varsa gidip “desktop” dosyasını kuruyor.

İşte bütün sorun burada. Sorun şu ki, bu betik her koşulda tüm desktop dosyalarını yüklüyor. Xscreensaver’den çıkan ekran koruyucularını hiçe sayıyor. Yukarıdaki şu satırlarda sorun var yani:

find_file(XSCREENSAVER_FILE_FOUND ${_screensaverName}.xml PATHS ${XSCREENSAVER_CONFIG_DIR} )
if(XSCREENSAVER_FILE_FOUND)
...

Tam olarak hatayı görmek için cmake’in MESSAGE komutu ile şu şekilde yazıp Makefile’ı çalıştırıyorum tekrardan:

find_file(XSCREENSAVER_FILE_FOUND ${_screensaverName}.xml PATHS ${XSCREENSAVER_CONFIG_DIR} )
MESSAGE(STATUS ${XSCREENSAVER_FILE_FOUND} )
if(XSCREENSAVER_FILE_FOUND)
...

Aldığım sonuç ise şu şekildeydi.

-- /usr/share/xscreensaver/config/compass.xml
-- xscreensaver name truchet found
-- name without extension
-- /usr/share/xscreensaver/config/compass.xml
-- xscreensaver name decayscreen found
-- name without extension
-- /usr/share/xscreensaver/config/compass.xml
-- xscreensaver name photopile found
-- name without extension
-- /usr/share/xscreensaver/config/compass.xml
-- xscreensaver name jigglypuff found

Burada “compass” ile bitenler bizim XSCREENSAVER_FILE_FOUND değişkenin değeri. Gördüğünüz gibi aslında bu değişken her file_find komutu çağırıldığında değişmesi gerekiyordu. Fakat bizde değişmiyor

Peki neden ?

Fatih Aşıcı ve Gökçen Eraslan ile bu konuya bakarken, Gökçen şu komutu file_find’dan önce yerleştirmemizi söylemişti

unset(XSCREENSAVER_FILE_FOUND CACHE)
find_file(XSCREENSAVER_FILE_FOUND ${_screensaverName}.xml PATHS ${XSCREENSAVER_CONFIG_DIR} )

Bu şekilde tekrar make komutunu çalıştırdığımda şu çıktıyı almaya başladım

-- name without extension
-- /usr/share/xscreensaver/config/truchet.xml
-- xscreensaver name truchet found
-- name without extension
-- /usr/share/xscreensaver/config/decayscreen.xml
-- xscreensaver name decayscreen found
-- name without extension
-- /usr/share/xscreensaver/config/photopile.xml
-- xscreensaver name photopile found
-- name without extension
-- /usr/share/xscreensaver/config/jigglypuff.xml

Evet görüldüğü gibi artık XSCREENSAVER_FILE_FOUND değişkeni güzel bir şekilde değişmeye başlamıştı. Bu satırı ekleyip baştan derletip kdeartwork’u kurunca herşey güzelce çalıştı. Peki bu satırı neden eklememiz gerekiyordu ?

Cmake’de file_find komutunun yardım dosyasını şu şekilde açarak:

cmake --help-command find_file

Aşağıdaki kısmı okuyunca her şey daha iyi anlaşıldı:

This command is used to find a full path to named file.  A cache entry
named by <var> is created to store the result of this command.  If the
full path to a file is found the result is stored in the variable and
the search <strong>will not be repeated unless the variable is cleared</strong>. </var>

Yani file_find komutu ilk bulduğu değeri belleğe atıp kaydediyor. Ondan sonraki değerleri hiçe sayıyor. Bellekteki değeri silmemiz bekliyor.”unset” komutu ile işte bu değişkeni bellekten siliyor tam olarak. Paketi yamalayarak sisteme kurduktan sonra aşağıda “Ekran Koruyucu“yu açtığımda gördüğünüz gibi “Değişen Çizgiler” ekran koruyucusunun yok olduğunu görürsünüz:

Hepsi bu kadar. Yine bir satırlık bir yama ama gördüğünüz gibi bir çok şeyi değiştiriyor. Bunun gibi yüzlerce hata çözüldüğünü, çok daha eksantrik olanların olduğundan bahsetmeye gerek yok herhalde. Bazıları gerçekten çok zaman alabiliyor. Örneğin benim yazdığım bir diğer yazımdaki hata 2-3 günümü alırken, bu hata 2 saatimi almıştı. Biraz da o anki ruh halin de etkisi olabiliyor herhalde.

Bu arada yukarıda anlatıklarımın çoğu sesli düşünce. Hataya bakarken belki yüzlerce komut kullanıyorsunuz, vim macrolarını aslında çok hızlı bir şekilde yazıyorsunuz. Ben sadece adım adım neler yapıldığını ve nerelere gidebileceğini gösterdim yine. Vim ile yaptığım düzenlemelerde biraz ayrıntıya girdim biliyorum, fakat onu da macro ve iki dosya arasındaki karşılaştırma olayların sıkça yapıldığından biraz ipucu şekilde olsun dedim.Sorularınız varsa daha detaylı anlatabilirim.

13
Ara

Bugzilla’ya girilen hataların çözüldüğüne şahit oluyorsunuz kimi zaman. Bunlar anında çözülse de bazıları zaman alabiliyor. Peki hiç bir geliştirici gözünden merak ettiniz mi bir hata nasıl kapanıyor diye ? Aslında basit gibi gözüken bir hatanın nerelere kadar gittiğine hep beraber inceleyelim. Uzun bir yazı, şimdiden uyararım:

1.) Bundan yaklaşık iki ay önce ilk hata giriliyor [1], bir ay geçmeden ikinci bir hata daha giriliyor [2], bir ay sonra yine aynı hata giriliyor [3]. Tüm hata kayıtları aynı:

Tasma’da simge setini “Siyah-Beyaz” çevirmek istediğinizde Tasma çöküyor. Evet bu kadar basit. “Siyah-Beyaz” seti bir türlü seçilemiyor. Aşağıdaki gibi bir hata ile karşılaşıyoruz:

“Geriye dönük izleme” sekmesine tıkladığımızda aşağıdaki hata kaydını görüyoruz:

[Thread debugging using libthread_db enabled]
0xffffe424 in __kernel_vsyscall ()
#0  0xffffe424 in __kernel_vsyscall ()
#1  0xb7788da0 in __nanosleep_nocancel () from /lib/libc.so.6
#2  0xb7788ba3 in __sleep (seconds=0)
    at ../sysdeps/unix/sysv/linux/sleep.c:138
#3  0xb6f0e2dc in KCrash::startDrKonqi(char const**, int) ()
   from /usr/kde/3.5/lib/libkdecore.so.4
#4  0xbff3ca4c in ?? ()

2.) Hata kaydından görüldüğü gibi pek bir şey anlaşılmıyor. Bu konuda yapabileceğimiz bir şey olmadığından en baştan başlayalım, yani simge setlerinin bulunduğu dizinden. Linux işletim sistemlerinde simge setleri genelikle /usr/share/icons altında kaydedilir. Fakat KDE simge setleri /usr/kde/3.5/share/icons/ dizini altında da yer alabiliyor. Biz de buradan başlayalım araştırmamıza.

“Siyah-beyaz” simge setimiz mono adıyla orada duruyor. Simge setinin dosyalarının .svgz formatında olduğunu gördüm.. SVG formatı kullanıyor ama GZ ile sıkıştırılmış yani. Orjinal boyutunun sadece %20′sini kaplıyor. Bu oran çok fazla olduğundan simge setleri genelikle bu şekilde paketleniyor.

3.) Simge setinin bozuk olacağını düşünürek kısa bir Google aramısından sonra sahibine ulaştım [4]. 2004 yılında simge setini oluşturumaya başlamış ve ve 2007 yılında güncellemiş. Ama KDE’nin Svn deposunda bulabileceğimiz söyleniyordu.  KDE deposundan indirip son sürümü denemek istedim. KDE’nin svn depoları yavaştan git’e dönüştüğü için düzgün bir sunucu bulup düzgün bir şekilde indirmek için çeşitli svn kombinasyonları denedim.  Sonunda Gökmen’in de yardımı ile şu satırı buluyoruz ve indirmeye başlıyoruz:

svn co svn://anonsvn.kde.org/home/kde/tags/KDE/3.5.10/kdeaccessibility/IconThemes/mono

Tasma’yı açıyor ve tekrar deniyoruz. Ama Tasma’nın yine çöküyor. Biraz daha farklı bakalım o zaman.

4) Ne demiştik. Tasma’da simge setine tıkladığımızda, Tasma tamanen kitleniyor ve çöküyor. Kısaca düşünürsek, simge setine tıkladığımızda Tasma’nın içindeki “icons” modülü simgeleri yükleyecek ve bir önizleme oluşturacak. Eğer bir önizleme oluşturamıyorsa, o zaman yüklemede bir yerde takılıyor ve yükleyemiyor demek. Bizim “Siyah-Beyaz” seti svgz formatından oluşacağından, ilk önce sıkıştırılmış dosyayı açacak. Peki, o zaman şu dosyaları kendimiz açıp bir bakalım, neymiş ne değilmiş.

$ gunzip örnek.svgz
gzip: devices/örnek.svgz: unknown suffix -- ignored

Hm… Bilmediğini iddia ediyor. Halbukji .svgz dosyaları GZ ile sıkıştırılmıştı. Hangi formatları desteklediğini öğrenmek için man gunzip ile açıp okumaya başlıyorum. Dökümantasyon’da şu cümle yazıyordu:

gunzip takes a list of files on its command line and replaces each file whose name ends with .gz, -gz, .z, -z, or _z (ignoring case) …

O zaman biz de bu kurala uyalım ve dosyanın son ekini .gz yapıp açalım

$ mv örnek.svgz örnek.svg.gz
$ gunzip örnek.svg.gz

Voila. Dosyamız açıldı. Fakat bir sorun var, sadece bir dosya açıldı. Tüm dosyaların son ekini svg.gz şeklinde yapıp tüm dosyaları gunzip ile açmak gerekiyor (Tasma tüm seti yüklesin, olması gerektiği gibi). Bash’deki for seçeneği çok kullanışlı olsa da, birbirine içine girmiş dosyalar söz konusu olunca find komutu daha işe yarar oluyor. .svgz ile biten tüm dosyaları .svg.gz’ye çevirmem gerekiyor. Rename komutu da İbrahim’in tavsiyesi, bu tavsiye olmazsa “sed” komutu ile uğraşmam gerekiyordu (sonradan ekleme: Gökçen sayesinde -S parametresinin varlığından haberdar oldum. gunzip -S .svgz ile doğrudan belirleyebiliriz son eki, find ile hepsini çevirmemize gerek yok yani :) ):

find  scalable/ -name *.svgz -exec rename .svgz .svg.gz '{}' \;

ardından gunzip ile hepsini açalım. gunzip’de güzel olan ise -r seçeneğinin olması. Bu seçenek ile klasör ismi verebiliriz ve içinde ne kadar klasör varsa her birine girip dosyaları açıyor.

gunzip -f -r scalable/

Evet tüm dosyaları .svg formatına dönüştürdük. Şimdi Tasma’yı tekrar açıyoruz ve “Siyah-Beyaz simge setine tıklıyoruz. Evet, açıldı!

Tasma çökmedi, ve güzel bir şekilde simge setimizi kullanabildiğimizi gördük. Demek svgz paketlerinde bir sorun var. Peki tam olarak nerede ?

5.) SVGZ formatını açan KDE fonksiyonunu bulup incelememiz lazım. Bunun hangi paketten çıkacağına pek emin değilim. Fakat fonksyonun başka kütüphanelerce kullanabileceği ihtimali var. Çünkü icon seti yükleniyor. Hemen hemen her uyguluma simge seti yüklediğinden, bu görev bir kitaplık tarafından yaptırılıyordur. Ozan ile soruna bakarken, bu fonksiyonun kdelibs’den çıktığını anladık. Fonksiyonun adı ise KSVGIconEngine()j Ben sonra araştırmalarımı bu yönde ağırlaştırmaya başladım. Hangi dosyalarda kullanıldığına baktım ilk önce:

grep -rin KSVGIconEngine .

Grep, parametre olarak verdiğimiz kelimeleri dosyanın içeriğinde satır satır arayıp bize yerini bildiren bir uygulama. “-rin” seçeneği ile alt dizinlerinin tümünü arayacak, ararken büyük-küçük ayrımı yapmayacak ve bulduğu kelimelerin dosya’daki satır numarasını da gösterecek. Satır numarası bize lazım, çünkü 4000 satırlık dosyalar olabiliyor. Arama sonucu baya uzun, ama şöyle bir göz gezdirip bakalım neler varmış:

...
./kdecore/svgicons/ksvgiconengine.cpp:513:     KSVGIconEngineHelper *helper;
./kdecore/svgicons/ksvgiconengine.cpp:518:KSVGIconEngine::KSVGIconEngine() : d(new Private())
./kdecore/svgicons/ksvgiconengine.cpp:520:      d->helper = new KSVGIconEngineHelper(this);
./kdecore/svgicons/ksvgiconengine.cpp:525:KSVGIconEngine::~KSVGIconEngine()
...

Aha !! İlk ipucunu yakaladık bile. Görüldüğü gibi gözümüze hemen ksvgiconengine çarpıyor. Herhalde bizim simge setini yükleyen fonksiyon bu olsa gerek. Fonkisyona ait ksvgiconengine.cpp dosyasını açıp inceleyelim. İncelereken gz_open fonksiyonuna dair bir şeyler de aramaya başladım. Aramaların sonucunda su kısma denk geldim:

...
gzFile svgz = gzopen(path.latin1(), "ro");
if(!svgz)
return false;

QString data;
bool done = false;

QCString buffer(1024);
int length = 0;

while(!done)
{
int ret = gzread(svgz, buffer.data() + length, 1024);
...

Burada gzopen() fonksiyonu bir seçenek alıyor ve onunla açıp svgz nesnesi oluşturuyor. Eğer patlıyorsa ve Tasma çöküyorsa gzopen() fonksiyounun bu hataya sebep olacağını düşünerek devam edelim (ihtimaller çok, ama en mantıklı bu gözüküyor). Ya da gzread ile okurken patlıyor olabilir. Önemli olan bu iki fonksiyondan biri buna sebep oluyor. Peki bu fonksiyon hangi pakete ait ?

6.) Yine bir google araştırması ile bu paketinin zlib paketine ait olduğunu öğreniyoruz. Kurumsal 2′deki paket 1.2.5 sürümünde. Eğer bu paket sorunluysa, ve gzopen() düzgün çalışmıyorsa son zamanlarda yaptığımız güncellemelerden kaynaklanabilir. Peki 1.2.5 sürümü ne zaman güncellendi ?

Commit listesine baktığımızda [5] 27.9.2010 tarihinde geçirilmiş gözüküyor.
Bizim hata da 07.10.2010 tarihinde açılmış

Yani Zlib güncelleniyor ve yaklaşık 1.5 Hafta sonra hata kaydı açılıyor. Demek ki bununla ilgili olabilir. Devam edelim ve paketin hangi sürümden güncellediğinde bakalım.

     <History>
+        <Update release="10">
+            <Date>2010-08-20</Date>
+            <Version>1.2.5</Version>
+            <Comment>Version bump and split devel packages.</Comment>
+            <Name>Ozan Çağlayan</Name>
+            <Email>ozan at pardus.org.tr</Email>
+        </Update>
         <Update release="9">
             <Date>2010-06-08</Date>
             <Version>1.2.3</Version>

Gördüğünüz gibi 1.2.3 sürümünden 1.2.5 yükseltmişiz (Paket 2011 deposundan merge edildiği için tarih eskiyi gösteriyor, ona aldanmayın). Acaba 1.2.3 sürümünü tekrar kursak hata düzelir mi ? Bunu deniyorum elbette ve zlib sürümünü 1.2.3 sürümüne downgrade yapıyorum. Sonrasında Tasma’yı açıp simge setini değiştire tıklıyorum.

Evet! ikinci bir Voila! Paket çökmedi ve çalıştı. Bu ne demek peki ?

7.) zlib 1.2.3′den 1.2.5′e geçerken gzopen() fonksiyonu değişime uğruyor ve ondan sonra çalışmamaya başlıyor. O zaman bu iki paketi indirip aradaki farka bakmamız gerekiyor. Zlib’in sitesine girip 1.2.3 ve 1.2.5 sürümünü indirmek için bağlantıya tam tıklayacaktım ki kötü süpriz ile karşılaştım [6]. 1.2.3 sürümü ve 1.2.5 arasında tam beş yıl geçmiş!

1.2.3 (18 July 2005)
1.2.5 (19 Apr 2010)

Tabi bununla da bitmiyor, bu iki tarih arasında onlarca minor sürüm çıkmış. Tam söylemek gerekirse :

zlib-1.2.3.tar.gz        18-Jul-2005
zlib-1.2.3.1.tar.gz     16-Aug-2006
zlib-1.2.3.2.tar.gz     04-Sep-2006
zlib-1.2.3.3.tar.gz     03-Oct-2006
zlib-1.2.3.4.tar.gz     22-Dec-2009
zlib-1.2.3.5.tar.gz     08-Jan-2010
zlib-1.2.3.6.tar.gz     17-Jan-2010
zlib-1.2.3.7.tar.gz     24-Jan-2010
zlib-1.2.3.8.tar.gz     13-Feb-2010
zlib-1.2.3.9.tar.gz     21-Feb-2010
zlib-1.2.4.tar.gz        14-Mar-2010
zlib-1.2.4.1.tar.gz     28-Mar-2010
zlib-1.2.4.2.tar.gz     09-Apr-2010
zlib-1.2.4.3.tar.gz     10-Apr-2010
zlib-1.2.4.4.tar.gz     18-Apr-2010
zlib-1.2.4.5.tar.gz     18-Apr-2010

Sürüm değişiklikleri ile ilgili değişiklikleri dizeleyen [6] dosyayı gzopen() fonksiyoununa aratmama rağmen ele tutulur hiç bir şeye varamadım. Peki şimdi hangi sürümden hangi sürüme geçiş yaparken sorun çıkıyor ? Sürümleri, üsten başlayarak rastgele seçtim ve 1.2.3.9 sürümünden 1.2.4 sürümüne geçişti gzopen() değişime uğradığını gördüm. Burada sormamız gereken soru ise, nasil bir değişime ?

8.) Aradaki farkı görebilmek için diff kullandım. 1.2.3.9 ve 1.2.5 sürümlerini siteden [7] indirip bir klasöre attım. Burada artık 1.2.4 yerine doğrudan 1.2.5 sürümünü indirdim, çünkü 1.2.3.9′dan sonraki tüm sürümlerde Tasma çöküyor. Bu sebeble en yeni sürüme ile 1.2.3.9 sürümü arasındaki farka bakmak daha yararlı olacak. gzopen ve gzread fonksiyonlarına baktım ve gzlib.c’den çıktığını gördüm. Bunların farkını diff komutu alınca

diff -Nur zlib-1.2.3.9/gzlib.c zlib-1.2.5/gzlib.c > degisim.patch

ilginç bir süpriz ile karşılaşıyoruz( kod parçasının sadece bir kısmını gösteriyorum):

--- zlib-1.2.3.9/gzlib.c·   2010-02-18 04:52:05.000000000 +0200
+++ zlib-1.2.5/gzlib.c· 2010-04-18 20:53:22.000000000 +0300
@@ -147,6 +147,14 @@
return NULL;
}
·
+    /* save the path name for error messages */
+    state-&gt;path = malloc(strlen(path) + 1);
+    if (state-&gt;path == NULL) {
+        free(state);
+        return NULL;
+    }
+    strcpy(state-&gt;path, path);
+
/* open the file with the appropriate mode (or just use fd) */
state-&gt;fd = fd != -1 ? fd :
open(path,
@@ -164,20 +172,13 @@
O_APPEND))),
0666);
if (state-&gt;fd == -1) {
+        free(state-&gt;path);
free(state);
return NULL;
}
if (state-&gt;mode == GZ_APPEND)
state-&gt;mode = GZ_WRITE;         /* simplify later checks */
·
-    /* save the path name for error messages */
-    state-&gt;path = malloc(strlen(path) + 1);
-    if (state-&gt;path == NULL) {
-        free(state);
-        return NULL;
-    }
-    strcpy(state-&gt;path, path);
-
/* save the current position for rewinding (only if reading) */
if (state-&gt;mode == GZ_READ) {
state-&gt;start = LSEEK(state-&gt;fd, 0, SEEK_CUR);

Diff’e alışık gözler burada bir yer değişikliğini olduğunu görecekler. Ayrıca bir bir pointer adresi temizliyoruz. Bu kod parçasında ilginç olan ve bizim Tasma’yı çöktüren neden, bir kod parçasının yer değiştirmesinden kaynaklanıyor. Bu iki dosyayı yan yan koyup incelediğimizde ise şunları görüyoruz (resime tıklayarak büyütebilirsiniz)

Sol tarafta 1.2.3.9 sürümü var, sağ tarafta ise 1.2.5 sürümü. Sol taraftaki sürümde Tasma çökmüyorken, sağ taraftaki sürümü kullanmaya başladığımzıda çökmeye başlıyor. Peki neden ? Burada zlib hakkında pek bilgim olmadığından geniş bir yorum yapamayacağım, fakat tahminen beklenen önce NULL değeri geri döndürdüğü için KSVGIconEngine bu geri döndürilen NULL ile bir şey yapamıyor. Bu konuda fikri olan varsa yorumlara yazarsa sevinirim : )

9.) Şimdi gelelim “path” olarak KDE’deki fonksiyonunun ne çevirdiğine. Fonksiyonun tam olarak nerede kullanıldığını ögrenebilmek için KSVGIconEngine fonksyionun nerelerde çağırıldığına bakmam lazım. Bunun için ksvgiconengine.h satırını bulmamız işimize yarayacak. Kaynak kodlarının yer aldığı /var/pisi dizinin altındaki kdelibs paketine girip aratınca:

grep -rin ksvgiconengine.h

Gelen sonuçlarda biri ise:

...
./kiconloader.cpp:50:#include "svgicons/ksvgiconengine.h"
...

Demek ksvgiconengine başlık dosyalarını kiconloader.cpp adındaki bir dosyada çağırılıyor. Madem öyle artık araştırmamıza bu dosya üzerinden yapmamız gerekiyor. Dosyada bir arama yapınca şununla karşılaştım:

#ifdef HAVE_LIBART
    else
    {
        // Special stuff for SVG icons
        KSVGIconEngine *svgEngine = new KSVGIconEngine();
        if(svgEngine->load(size, size, icon.path))
        img = svgEngine->painter()->image();
        else
        img = new QImage();

        delete svgEngine;
    }
#endif

Burada görüldüğü gibi bizim fonksiyon çağırılıyor. Parametre olarak da simgenin boyutlarını ve dosyanın yolunu veriyoruz. Evet, görüldüğü gibi, buradaki icon.path bizim başımızı ağrıtıyor. Sorunun kökü ta kendisi. Yukarıdaki fonksiyon temel fonksiyonlardan biri. Çoğu uygulama bunu kullanacak. Tasma’da simge setini değiştiriken de bu fonksiyon çağırılıyor. Burada şunu bilmemi lazım. “kdelibs” paketi yukarıdaki kod parçasını bize sağlıyor(yani ksvgiconengine()). Simge setini değiştirdiğimiz modülün adı ise “icons”. Bu modül ise “kcontrol” ait bir modül ve “kdebase” paketinden çıkıyor. Tasma’da bu simge setini seçtiğimizde “icons” modülü çağrılıyor.

10. Bu sefer aramak yerine, tema’da tıkladığımız listenin nesnesine ve sinyali gönderdiği yere bakmaya karar verdim. Simge setinin oluşturan liste ise şu :

  m_iconThemes=new KListView(this,"IconThemeList");
  m_iconThemes->addColumn(i18n("Name"));
  m_iconThemes->addColumn(i18n("Description"));
  m_iconThemes->setAllColumnsShowFocus( true );
  m_iconThemes->setFullWidth(true);
  connect(m_iconThemes,SIGNAL(selectionChanged(QListViewItem *)),
        SLOT(themeSelected(QListViewItem *)));

Görüldüğü gibi tıkladığımızda sinyal “themeSelected” fonksiyonunu çağırıyor. Dosya içinde o fonksiyonu aratınca şuraya varıyoruz(uzun bir yer sadece en önemli kısmını gösteriyorum):

void IconThemesConfig::themeSelected(QListViewItem *item)
...

  KIcon icon=icontheme.iconPath("exec.png", size, KIcon::MatchBest);
  if (!icon.isValid()) {
#ifdef HAVE_LIBART
      icon=icontheme.iconPath("exec.svg", size, KIcon::MatchBest);
      if(engine.load(size, size, icon.path))
              m_previewExec->setPixmap(*engine.image());
          else {
              icon=icontheme.iconPath("exec.svgz", size, KIcon::MatchBest);
              if(engine.load(size, size, icon.path))
                  m_previewExec->setPixmap(*engine.image());
          }
#endif
  }
...

Burada .svg dosyalarını yüklüyor eğer if döngüsü “false” döndürürse .svgz dosyasını yüklüyor.

Peki bu if döngüsü bize “false” döndürmezse ne oluyor ? Evet Tasma çöküyor :)

Eğer “false” döndürebilseydi o zaman .svgz dosyalarını yükleyebilecekti. Çünkü o dosyalar elimizde mevcut. Burada .svg dosyasını gzopen ile açmaya çalışıyor, fakat “path” değişkeni yazımın ortalarında da değindiğim gibi NULL değeri aldığından gzopen patlıyor. Çözüm artık kesinleşti gibi.

if(engine.load(size, size, icon.path))

satırının düzetmemiz lazım. 5′inci satırda eklediğim gz_open dosyasında şu satırları:

bool KSVGIconEngine::load(int width, int height, const QString &path)
{

    QDomDocument svgDocument("svg");
    QFile file(path);

aşağıdaki gibi değiştiriyoruz.

bool KSVGIconEngine::load(int width, int height, const QString &path)
{
    if (path.isEmpty())
        return false;

    QDomDocument svgDocument("svg");
    QFile file(path);

Evet, boş bir “path” değişkeni geldiğinde bize “false” döndürecek iki satırlık bir şey. Hepsi bu kadar :) Bu yama bir saat önce svn deposuna da yansıdı. Yazdıklarım sanki bir anda yapılmış gibi gözükebilir, fakat 2-3 günümü aldı. Kullanıcı açısından basit gibi gözüken bir şey, biz geliştiricilerin bazen günlerini alabiliyor. Buraya da not düşeyim :)

[1] http://bugs.pardus.org.tr/show_bug.cgi?id=14560
[2] http://bugs.pardus.org.tr/show_bug.cgi?id=15022
[3] http://bugs.pardus.org.tr/show_bug.cgi?id=15440
[4] http://kde-look.org/content/show.php/Monochrome?content=18317
[5] http://liste.pardus.org.tr/paketler-commits/2010-September/107492.html
[6] http://www.zlib.net/ChangeLog.txt

24
Mar

Bu hatayı aldığınızda öncelikle listelerde geçerli olan remove(int index) fonksiyonunun referans sayfasına bakalım;

Throws:
UnsupportedOperationException - if the remove method is not supported by this list.

Aslında hata gayet açıklayıcıydı ama başka tür hatalar aldığınızda da referans sayfasına bakmanızda fayda olduğundan bu aşamayı da yazdım.

Ben bu hatayı Arrays.asList ile liste oluşturduğumda aldım;

        List enumList = Arrays.asList(ApplicationStatus.values());
        enumList.remove(3);

Bu kodun amacı ApplicationStatus sınıfındaki enum degerleri bir listeye atıp ardından bazı elemanları silme işlemi yapmaktı.

Bu şekilde asList ile oluşturulan listeler java.util.Arrays$ArrayList gibi bir liste döndürüyor ve bu tip listeler remove() add() gibi fonksiyonları desteklemiyor. Bu sebepten UnsupportedOperationException hatasını alıyoruz.

Bunun çözümü bir şekilde düzgün bir liste oluşturmak. Ben şöyle yaptım;

        List enumList = Arrays.asList(ApplicationStatus.values());
        List realList = new ArrayList();
        for (int i = 0; i &lt; enumList.size(); i++) {
            realList.add(enumList.get(i));
        }
        realList.remove(3);

Artık realList ile istediğim gibi remove(), add() fonksiyonlarını kullanabiliyorum.