Posts Tagged ‘сорс’

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

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-майстор може да се произнесе с идеи 🙂

Hello, Android!

Tuesday, April 23rd, 2013

От едно време насам като се прибера вкъщи и си пускам да слушам онлайн радиа. А от както открих Digitally Imported и си лягам с пуснато радио.

Понеже аз съм доста мързелив тип (признавам си), по едно време ми стана тежко както си седя на дивана с крака върху масата да ставам да ходя до десктопа и да сменям радиото или да го спирам. Слава богу – има SSH и mplayer, така че тези неща мога да ги правя от лаптопа който се намира на фундаменталното разстояние на 3 метра от бюрото. Другия проблем е че искам радиото да се спира половин час след като си легна. Има много приспиващи станцийки, но не искам да бучат през цялата вечер.

Първия ми порив беше да сложа rc0.d скрипт на лаптопа, който преди shutdown да се вързва към десктопа и да прави някаква магия с `at` и `killall mplayer` която да гаси радиото след 30 минути. Но това щеше да бъде много лесно, а пък ми се занимаваше с нещо ново.

И нали сега са модерни мобилните приложения, реших да пробвам да си напиша апп за телефона. Свалих официалния ADT-bundle с инструментите за разработка и почнах. Тук да спомена че с Java имам един-единствен опит от университета и нищо повече. Какви са ми впечатленията:

Eclipse
+ той бил много читав редактор с всичките нужни code completion-и, javadoc popup-и, syntax highlights и т.н.
+ превъзходна интеграция с дебъгера на андроида – разработчиците от Google са си свършили работата
+ интеграция с емулатора, който е идеален за тестване на бутони, поведение… доста е тежък обаче и този апп през повечето време съм го тествал директно на телефона
+ logcat – инструмент, който вади директно дебъг съобщенията от емулатора или телефона в еклипса. Първия път като го свързах към телефона и се шашнах. МАЛЕ, всички нишки и процеси продуцират огромно количество дебъг съобщения и exceptions 😀 Хората не си ли махат дебъга в продукшън версиите на приложенията?
+ понякога logcat решава че няма да работи повече, което се оправя с рестарт на IDE-то
+ еклипса много ограничава писането на кода в добрия смисъл – не те оставя да пишеш глупости, за всеки проблем (примерно assign type mismatch) излизат възможните решения и кода често се пише с цък от мишката
+ кода наистина се пише с цък от мишката – ако ви трябва да имплементирате метод от parent class, 3 клика и шаблона ви се налива във вашия код
+ няма вградена интеграция с SVN
+ от много ум понякога изтрещява и започва да дава несъществуващи грешки, което се оправя с рестарт на IDE-то
Java
+ струва ми се много странен език като идвам от web програмирането
+ по-ниска степен на абстракция от web езиците и следователно:
+ нещата стават с адски много писане. Примерно един file_get_contents(“http://domain.com”) от PHP в Java става с 30 реда плътно изписан код, в който се ползват 15 различни типа обекти
+ няма асоциативни масиви, което сериозно ми бърка в здравето. Има разни workaround-и като да се ползват обекти, ама не е същото.
+ много грозни stack trace-ове, на които още не мога да свикна 🙂

Android
+ доста подреден lifecycle на приложенията
+ подробна документация на официалното място, но ми се струва доста суха. В Stackoverflow обаче има хиляди материали с примерен код
+ във връзка с горното, 90% от писането на кода е да намериш вече решения проблем и да го copy-paste-неш правилно 🙂
+ нишки и кой код в коя нишка се изпълнява… редовно се опитвам да достъпвам елементи от UI-нишката от друга нишка и естествено не се получава 🙂 Другия проблем е програмно да си извадите бавните операции в задна нишка, защото иначе се получава лагване на апп-а.
+ layouts, където си седи презентационната логика и activities, където си седи бизнес логиката
+ broadcast receivers, или слушатели за глобални събития
+ запазване на settings между различните изпълнения на апп-а, обаче settings трябва да са от много примитивен тип
+ куриозно е да се опиташ да натъпчеш UI в такива малки разделителни способности – буквално след третото текстово поле и мястото свършва 🙂
Първата идея беше на десктопа да направя няколко PHP скрипта, които общо взето да правят system(‘mplayer нещоси’) и от телефона да ги изпълнявам с HTTP GET заявки. Но се оказа че, да направиш HTTP GET в Android е грозно, голямо и ужасно, поне за текущите ми умения, така че тази идея беше отхвърлена още на първата вечер. Втората идея беше да се ползва SSH като от телефона директно се пращат командите към десктопа. Това се оказа доста по-лесно, че и целия апп стана много универсален.

И ето го апп-а My Multimedia Controller, който вече взе да придобива малко търговски вид. Качвам и кода, някой ако иска да ми се подиграва 🙂 Сериозно, не бих се подписал под него и мисля че трябва да бъде принтиран на хартия и ритуално изгорен. Обаче пък приложението работи.
Features:
+ управлява нивото на звука на десктопа
+ превключва измежду няколко радио станции
+ запомняне на “домашна WiFi мрежа”. Ако се конектнете към нея, ще получите нотификация, от която да се пусне апп-а
+ sleep mode – таймер за спиране на радиото, точно като sleep timer-а на телевизорите 🙂

Prerequestes:
+ Linux desktop, `amixer` за контрол на нивото на звука (`amixer` вече не е задължително изискване от r17 натам), `mplayer` за свирене на радио станциите
+ Android 2.2+ device

Required permissions:
+ INTERNET – за да работи SSH
+ ACCESS_NETWORK_STATE и ACCESS_WIFI_STATE – за нотификацията при конект към домашната мрежа. В една по-ранна версия на приложението при такъв конект се пускаше самото приложение, но с практиката се оказа че това е доста intrusive behavior и оставих само нотификация 🙂

 

TODO:
+ профили за SSH – да запазва данните за конект за повече от 1 PC добавено в r13
+ повече контрол върху различни миксер канали и възможност за софтуерно volume добавено в r17
+ динамично въвеждане на радио станции, че в момента URL-тата са хардкоднати
+ иконката да се смени с по-добра
+ обратна връзка за текущо свирената песен (ICY info тага, ако го има)

 

 

Download:

Project r20 – 1.7MB

APK r20 – 337KB

Не е финална версия 🙂

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 решения, които са създадени точно за това.