Archive for the ‘code’ Category

Записване на онлайн радио

Thursday, April 3rd, 2014

От известно време слушам Eurodance канала на Digitally Imported. Отвреме навреме пускат уникални миксове, които не могат да бъдат намерени никъде из нета. За това се захванах и написах едно кратко скриптче за да ги рипна.

Радиото излъчва ICY таг, който съдържа името на песента. По този таг може да се разделят песните в общия стрийм, като единствения кусур е че идва с няколко секунди закъснение или предварение.

Използвах -dumpstream опцията на mplayer за записване на потока. Паралелно с това върви още един mplayer процес, който наблюдава за пристигането на ICY таг и пуска ново записване в нов файл. Скрипта създава нова директория за всеки ден, като песните които запазва са с префикс на времето в което са излъчени.

  1. #!/bin/bash
  2.  
  3. # задаваме някой директории и файлове
  4. FIFO=/tmp/mplayer_recording
  5. OUT_DIR=/home/ivanatora/di.fm
  6.  
  7. LAST_PID=0
  8. LAST_SONG=""
  9.  
  10. if [ ! -e "$FIFO" ]
  11. then
  12.     mkfifo $FIFO
  13. fi
  14.  
  15. # това е процесът, който следи таговете с имената на песните и ги изпраща в едно FIFO
  16. nohup mplayer -ao null http://pub3.di.fm:80/di_eurodance | stdbuf -o L grep ICY > /tmp/mplayer_recording &
  17.  
  18. # тук четем от FIFO-то и пускаме нов записващ процес при получаване на ICY таг
  19.  
  20. while read LINE < $FIFO; do
  21.     DATE=`date +%F`
  22.     if [ ! -d "$OUT_DIR/$DATE" ]
  23.     then
  24.         mkdir "$OUT_DIR/$DATE"
  25.     fi
  26.  
  27.     echo "—–"
  28.     echo $LINE
  29.     # името на песента е заградена в единични кавички
  30.     # @TODO: понякога в самото име има единични кавички – да се обмисли този случай
  31.     SONG=`echo $LINE | cut -d\‘ -f2 | sed -e ‘s/ /_/g‘`
  32.    echo "Last: $LAST_SONG"
  33.    echo "Current: $SONG"
  34.    # тук имам един бъг някъде – получавам таговете по два пъти. Това е workaround, който дава да се продължи само ако тагът който идва е различен от предния.
  35.    if [ "$SONG" != "$LAST_SONG" ]
  36.    then
  37.        TIME=`date +%T`
  38.        echo "##### $TIME"
  39.  
  40.        # ако вече имаме записващ процес – време е да го спрем и да пуснем нов
  41.  
  42.        if [ $LAST_PID -ne 0 ]
  43.        then
  44.            kill $LAST_PID
  45.        fi
  46.  
  47.        nohup mplayer http://pub3.di.fm:80/di_eurodance -dumpstream -dumpfile "$OUT_DIR/$DATE/$TIME"_"$SONG".mp3 &
  48.        LAST_PID=$!
  49.        LAST_SONG="$SONG"
  50.    fi
  51. done

Тук имам следние проблеми:
1) Таговете ми идват по два пъти.
2) Закъснението на таговете, което не е константа, която може да бъде уловена и компенсирана. На практика няколко секунди от началото на песента се записват в предния файл.
3) Предполагам че може да бъде оптимизирано и направено само с 1 mplayer процес.
4) Ако ми умре нета за секунда, mplayer-ите се прекратяват, което минава през фифото и цикъла `while read` заминава.

Някой bash-майстор може да се произнесе с идеи 🙂

ExtJS 4 bouncy box animation

Monday, November 5th, 2012

Do you know ExtJS has some nice animation features? Here it is a very simple notification box that “pops in” from outside the browser.

It is pretty straightforward – show a window outside the viewport and use the Window.animate() method. `animate` is inherited from AbstractComponent, so it is usable on all kinds of components.
However, there is one gotcha. If you initially render the window on the right or bottom outside the screen (i.e. coordinates 10000,10000) the viewport will slide to keep the shown window in focus. I’m not sure why it does it. So, we do it a bit tricky – first show the window with negative coordinates (viewport will not slide in that way) and second – ensure the window is really shown before attempting to do `setPosition()` or `animate()`. According to the docs `showAt()` should return `this` and be chainable, but in my testing invironment it returns undefined… I’ve put a dumb setTimeout() – don’t do that if you have choice – chain showAt() or use onShow listener.

Using my sample code you should first create a NS.TestAnimationBox component and then do a `createWindow()` on it.

Ext.define('NS.TestAnimationBox', {
    extend: 'Ext.window.Window',
    width: 250,
    height: 150,
    layout: 'fit',
    title: 'Reminder',

    constructor: function(cfg){
        this.initConfig(cfg)
        this.items = this.getItems();
        this.callParent(arguments);

        var me = this;

        me.showAt(-2000, -2000); // initial 'show' outside browser
    },

    slide: function(){
        var me = this;
        var iViewHeight = Ext.getBody().getViewSize().height;
        var iViewWidth = Ext.getBody().getViewSize().width;

        me.setPosition(iViewWidth + me.width - 10, iViewHeight - me.height - 40); // adjust position just outside on the right
        me.animate({
            duration: 750,
            easing: 'bounce-out',
            to: {
                x: iViewWidth - me.width - 10
            }
        })
    },

    getItems: function(){
        var me = this;

        return [
            {
                bodyPadding: 10,
                html: 'Some text here',
                buttons: [
                    {
                        text: 'Reset',
                        handler: function(){
                            me.slide();
                        }
                    },{
                        text: 'Close',
                        handler: function(){
                            me.setPosition(-2000, -2000);
                        }
                    }
                ]
            }
        ]
    },

    createWindow: function(){
        var me = this;

        setTimeout(function(){
            me.slide();
        }, 100); // just to be sure there it is time interval between showing and sliding
    }

});

ExtJS 4 – clear/reset combobox

Friday, August 3rd, 2012

Surprisingly there is no native way to reset the value of a combo box. Once the user selects something in it, it can’t go back to ‘clear’ state. From this moment on the user can change the value with some other value, but can’t remove it at all. It is possible to use .setValue(”) on the ComboBox component to clear it programatically. But how exactly would you do it? Some people put additional form button with on click handler that finds the combo and .setValue-s it to empty. Other mix the combo with triggerfield and create another trigger that does the same.

These are all nice options, but are completely useless when it comes to the user’s uncanny ability to mess things up. I’ve seen a tons of users that do the following:
1. select some option in the combo
2. say ‘oh, I do not need this here’
3. mark the text in the combo’s textfield and delete it
4. hit submit
What is wrong here? The actual combo value is not dependant of the displayed text. If you remove the text, the value is still there, hidden behind the scene. And when you submit that form, the old combo value will be there and will be send to the server. And users do it all the time, it doesn’t matter if there is a big red button right next to the field that says “clear that combo”.

The easier solution for all these problems is to play along with the user. Make the combo clear its value when the text from the textfield is removed. It is not that complicated:

{
    xtype: 'combo',
    name: 'department_id',
    fieldLabel: 'Department',
    store: me.getDepartmentStore(),
    displayField: 'name',
    valueField: 'id',
    enableKeyEvents: true,
    listeners: {
        keyup: function(cmp){
            if (cmp.lastValue == ''){
                cmp.lastValue('');
            }
        }
    }
}

ComboBox is descendant of Ext.form.Field.Text so you can use its key events. When the user presses BACKSPACE or DEL to remove the text, the keyup event will fire and it will check the lastValue property. Both `rawValue` and `lastValue` hold the actual text in the textfield, so if this property is empty the combo should be cleared.

Entity–attribute–value data model in MySQL

Tuesday, June 19th, 2012

Принципно се въздържам да давам акъли на тема дизайн на БД понеже не се имам за кой знае какъв специалист. Напоследък обаче виждам доста хора да попадат в един капан, който им коства усилия, време и пари.

Entity–attribute–value (EAV) е един интересен модел за представяне на данни, който се различава от традиционалния релационен модел. При него данните се представят чрез тройки “запис”-“свойство”-“стойност”. EAV модела още може да се нарече “вертикален модел за представяне”, докато табличния е “хоризонтален”. Примерно записът “Ситроен С5” може да се представи с: “тип” = “хечбек”, “гориво” – “дизел”, “обем двигател” = “2200”, “скоростна кутия” = “автоматична” и т.н. В една релационна таблица това би изглеждало така:

+------------+--------+---------------+-----------------+
|    Име     | Гориво | Обем двигател | Скоростна кутия |
+------------+--------+---------------+-----------------+
| Ситроен С5 | Дизел  |          2200 | Автоматична     |
| VW Golf    | Бензин |          1600 | Ръчна           |
+------------+--------+---------------+-----------------+

Докато в един EAV модел представен таблично би изглеждало така (вижда се и защо се казва “вертикално представяне”):

+-------+-----------------+-------------+
| Запис |    Свойство     |  Стойност   |
+-------+-----------------+-------------+
|     1 | Име             | Ситроен С5  |
|     1 | Гориво          | Дизел       |
|     1 | Обем двигател   | 2200        |
|     1 | Скоростна кутия | Автоматична |
|     2 | Име             | Vw Golf     |
|     2 | Гориво          | Бензин      |
|     2 | Обем двигател   | 1600        |
|     2 | Скоростна кутия | Ръчна       |
+-------+-----------------+-------------+

Колоната “Запис” трябва да групира логически двойките в отделни записи.

Защо би се приискало на някой да използва EAV? Лесно се добавят нови полета, схемата на данните е отворена. Ако на късен етап от живота на нашия проект се окаже че за колите трябва да се пазят броя на гумите, просто въвеждаме ново свойство: “брой гуми” = “4”. Всичко е там – можем да разграничим отделните записи, можем да добавяме абсолютно произволни данни, можем да взимаме кой да е запис с всичките му полета. Ако ни потрябват всичките коли с обем на двигателя над 2000 съвсем лесно можем да вземем този списък. Звучи като песни и рози, особено ако трябва да пазите много различни типове документи и не ви се занимава с MySQL таблици от по 30-40-50 полета. Ако мислите така, значи сте с двата крака в капана.

Ако все още четете това и имате чувството че това четиво е насочено за вас – никога, ама никога не правете EAV в MySQL. Ако ви трябва за нещо елементарно (примерно – user preferences) – става, но ако имате голяма система, която има тенденция да се развива – не използвайте EAV.

MySQL е система за релационни бази данни. EAV не е релационен модел. Данните представени в EAV не са нормализирани.
Някои от важните features които липсват в EAV:
1. Метаданни. Не знаете кое поле какъв тип е. Нямате допустими стойности за полетата. Нямате валидиране на данните. DB енджина ще допусне да въведете “джинджипляктор” в стойността на “Обем двигател”. Не можете да използвате MySQL-ските типове данни по колоните.
2. Свързаност между записите. Няма foreign keys. Ако искате да намерите шофьора на дадена кола, ще трябва да правите self join на таблицата, вложени SELECT-и или две последователни заявки. Ако искате да намерите жената на шофьора на дадена кола, може да си направите харакири.
3. Индексиране. Данните в колоната “Стойност” са разнородни не могат да бъдат индексирани по смисъл.
4. Подреденост – няма колона по която могат да се подредят данните.
5. Странициране – по какъв критерий ще страницирате?
6. Агрегатни функции – не могат да се използват директно.

Как да филтрираме записа който ни трябва? Ако ни трябват всички коли с обем на двигателя над 2000 в релационна таблица това би било така:

SELECT id FROM cars 
WHERE engine_volume > 2000

В EAV таблица е почти същото:

SELECT id FROM fields 
WHERE attribute = 'engine_volume' AND value > 2000

Ако искаме да вземем само дизеловите автомобили с марка ситроен? В релационна таблица е елементарно:

SELECT id FROM cars 
WHERE fuel = 'diesel' AND brand = 'citroen'

В EAV може би ще го направим така:

SELECT id FROM fields 
WHERE (attribute = 'brand' AND value = 'citroen') 
    AND (attribute = 'fuel' AND value = 'diesel')

Да, ама не. Така написана заявката няма да върне нищо. Никога няма ЕДИН ред в който едновременно да има атрибут ‘brand’ и атрибут ‘fuel’. Може да пробваме така:

SELECT id FROM fields a
    INNER JOIN fields b
    ON a.id = b.id
WHERE 
    (a.attribute = 'brand' AND a.value = 'citroen')
AND
    (b.attribute = 'fuel' AND b.value = 'diesel')

Таблицата fields обикновено е огромна (все пак всяко поле е отделен ред) и си представете какво става при JOIN на огромна таблица със самата себе си… А ако търсим по три критерия? Три JOIN-a.
Алтернатива:

SELECT id FROM fields
WHERE value IN ('citroen', 'diesel') 
GROUP BY id
HAVING COUNT(*) = 2

Това може да работи ако търсените стойности са определени, но не и ако се търси нещо от вида “по-голямо”, “по-малко”, “съдържа низа”.

EAV е пример как “данните са там, ама не ни вършат работа”. Оказва се че основните MySQL функционалности трябва да се имплементират програмно и докато се усетим вече си пишем собствен DB енджин… Има ситуации в които наистина е необходим отворен дизайн на базата – в такъв случай съществуват NoSQL решения, които са създадени точно за това.

Регулярни изрази в C (PCRE)

Thursday, November 19th, 2009

Предполагам че на всеки, който пише код рано или късно му се налага да парсва низове. В случай че низът не е нещо кратко, точно и ясно, се налага използването на regex. В различните езици регулярни изрази се ползват по различен начин. Общото е че върху изследвания низ се налага шаблон и според това дали низът попада в шаблона (match-ва) се предприемат някакви действия или части от низа се прехвърлят в други променливи. В Perl това става лесно – има си езикова конструкция:

  1.  
  2. $string = "The lazy dog jumped over the quick brown fox.";
  3. if ($string =~ /(\w{3}\./){
  4. $match = $1;
  5. do_something_on($match);
  6. }
  7.  

Кракто и ясно – ако низът попада в /шаблон/ – да се направи еди какво си. Няма да се спирам точно върху синтаксиса на шаблоните, понеже самия факт че четете това тук предполага че сте на ясно 🙂
Все пак ако не сте на ясно, ето ви един плашещ шаблон, за да се откажете още тук:
/^([\d\.]+?)\skill\s+(\d+)\s+([\w\s]+?)(\d+?)\s+([\w\s]+?)\w+$/
Това използвам за парсване на тези редове от логовете на Unreal Tournament:
175.48 kill 1 Sniper Rifle 5 Sniper Rifle Decapitated
В PHP няма езикова конструкция, но има доста функции за Perl Compatible Regular Expressions, които работят на същия принцип.
Сега да видим как е положението в С.
От много време търсех как мога да ползвам регулярни изрази, но все удрях на камък. Когато ми се е налагало да парсвам низове съм ползвал sscanf или разбиване на символи, броене, чакане Х-тия символ да е точно ‘А’, Y-тия да е точно ‘B’ и т.н. Неприятна работа включваща жонглиране с цикли, if-ове, strcmp и двадесет нива индентация на кода.
Днес открих че в С има поддръжка на PCRE и ми се дощя да се самоубия 🙂
Ако работите на нормална операционна система, то може да видите много информация с man pcre и man pcreapi. Ако сте наистина щастливци, някъде из дистрибуцията ще имате файл pcredemo.c, който нагледно показва как се работи с PCRE функциите.
Ако не сте от тези щастливци можете да си намерите файла от тук.
Използват се две основни функции: pcre_compile и pcre_exec. Първата поема съставения шаблон от потребителски низов вид и го превръща във вътрешното представяне, което компилатора използва. Втората функция налага шаблона върху тествания низ и връща съвпаденията във вид на масив от вектори, сочещи началните позиции на съвпаденията. Можете да прочетете за функциите в съответните man страници. И най-добре вижте pcredemo.c