среда, 5 ноября 2014 г.

Иконка скайпа в Ubuntu 14.04

В Ubuntu 14.04 c Unity устанавливаем скайп из deb-пакета. Запускаем, не появляется иконка в системном трее.

Проблема связана с тем, что в Ubuntu 14.04 вообще нет никакого системного трея, поэтому даже отлично работавшие ранее способы с изменением настроек через dconf-editor уже не помогают. Помогает установка следующего пакета.

sudo apt-get install sni-qt:i386

sni-qt - плагин Qt, который превращает все старые QSystemTrayIcon в новые модные StatusNotifierItems. Теоретически решает проблемы не только скайпа, но и всех приложений, работавших когда-то с системным треем.

вторник, 2 сентября 2014 г.

Первый Google Apps Script

Уже достаточно продолжительное время сервисы Google являются одним из моих основных рабочих инструментов, поэтому неудивительно, что возникло желание разобраться, как можно ещё "дотюнить" их под свои нужды. Не то, чтобы у меня была какая-то критическая необходимость в определённых, ещё не реализованных, функциях, но добавить немного автоматизации для нескольких рутинных операций совсем не помешало бы.

Для написания своих скриптов к Google Apps существует специальный клон JS - Google Apps Script. Его использование не требует установки дополнительного ПО. Писать код, отлаживать и запускать можно прямо в браузере.

Для создания скрипта нужно открыть в браузере Google Disk, нажать кнопку "CREATE" и выбрать в выпадающем списке пункт "Script". Если такого пункта нет, то нужно подключить приложение Google Apps Script к диску. Делается это с помощью диалога управления приложениями, запускающегося из меню настроек диска. В процессе создания нового скрипта будет предложено выбрать один из готовых шаблонов для популярных приложений. Можно поэкспериментировать с шаблонами, а можно выбрать "Blank Project". Особого смысла в использовании шаблонов я не увидел.

В качестве "пробы пера" напишем скрипт, который будет запускаться раз в час, проверять папку "tmp" на гугл-диске и, если в папке содержатся файлы zip-архивов, будет распаковывать их в эту же папку.

Назовём проект "autounzip". Так же назовём и функцию, которая будет содержать весь код, реализующий задуманное.

function autounzip() {
  
  // Проверяем все папки 'tmp' на гугул-диске
  var folders = DriveApp.getFoldersByName('tmp');
  while (folders.hasNext()) {
    var folder = folders.next();

    // Ищем все сохранённые zip-архивы
    var contents = folder.searchFiles('mimeType="application/zip"');
    while (contents.hasNext()) {
      var fileContents = contents.next();
      var unzipblobs = Utilities.unzip(fileContents.getBlob());
      for (var i = 0; i < unzipblobs.length; i++) {        
        folder.createFile(unzipblobs[i]);
      }
    }
  }    
}

После запуска скрипта появится сообщение "Authorization required". Нажимаем кнопку "Continue". В открывшемся окне нажимаем "Accept". Этими действиями говорим гуглу, что мы не против, если наш скрипт получит доступ к данным диска. После разрешения доступа скрипт появится в списке подключенных к диску приложений.

Теперь нужно добиться, чтобы написанный скрипт запускался каждый час. В меню документа выбираем пункт "Resources\Current project's triggers". Добавляем новый триггер с параметрами Run - autounzip, Events - Time-driven, Hour timer, Every hour. Сохраняем триггер. Всё. Осталось дождаться автоматического запуска этого скрипта и проверить результат его работы.

Официальный скринкаст от Google с созданием простенького скрипта можно посмотреть на ютубе.

вторник, 26 августа 2014 г.

Ошибка при запуске screen

Узелок на память.

Последнее время очень часто сталкиваюсь с этой проблемой. При попытке запустить команду screen в консоли linux выскакивает сообщение об ошибке: Cannot open your terminal '/dev/pts/X' - please check. Важно! Перед выполнением screen производилось переключение между пользователями при помощи sudo su - user2.

Исправляется предельно просто. Выполняем в консоли:

script /dev/null

После этой команды screen запускается как нужно.

Пройдя по ссылке, можно прочитать объяснение, почему это работает.

воскресенье, 23 марта 2014 г.

Коллега, будь аккуратен

Передача произвольного числа параметров в функцию

Есть в PHP удобная, на первый взгляд, возможность - функции с переменным числом параметров. При вызове такой функции ей в качестве параметров через запятую передаётся произвольное количество значений. При этом внутренняя логика вызываемой функции содержит специальный код, который позволяет определить, сколько параметров ей передали, и что именно в них содержится. Делается это совсем не сложно с помощью стандартной библиотеки PHP.

Вот, например, функция, которая все передаваемые ей параметры выводит в столбик на экран.

function printVars() {
    $argsCount = func_num_args();
    $argsArray = func_get_args();
    for ($i = 0; $i < $argsCount; $i++) {
        echo $argsArray[$i] . "\n";
    }
} 

printVars('value 1', 'value 2', 'value 3');

Результат:

   value 1
   value 2
   value 3

Всю специальную логику обеспечивают два обращения к стандартной библиотеке: func_num_args для определения количества переданных параметров и func_get_args для доступа к этим параметрам.

Всё в этом коде хорошо, пока параметры мы передаём явно, но давайте представим такую, достаточно стандартную, ситуацию. Мы получили из базы данных какую-то запись, преобразовали её в одномерный массив и хотим передать элементы массива в функцию printVars. Сколько будет элементов массива, мы заранее не знаем. Вопрос на засыпку - как вызвать нашу функцию?

$valuesArray = array('value 1', 'value 2', 'value 3');
printVars(/* ??? */);

И вот тут, похоже, что-то в PHP недопроектировали. Нет решения данной задачи, которое бы позволило программному коду остаться наглядным и понятным. Первое, что приходит в голову, eval.

$quotesValuesArray = array_map(
    function ($item) { return "'$item'"; }, 
    $valuesArray);
eval("printVars(" . implode(', ', $quotesValuesArray) . ");");

Вариант получается неудобный и может стать источником непредсказуемых трудноуловимых ошибок. Этим, в принципе, страдает любой код, вызываемый через eval. Ужас, ужас!

Интереснее выглядит использование функции call_user_func_array. Она умеет вызывать некую пользовательскую функцию и передавать ей в качестве параметров значения из массива.

call_user_func_array("printVars", $valuesArray);

Код выглядит не идеально, но явно нагляднее, чем первый вариант. Да и мест для потенциальных ошибок стало меньше.

Если мы написали не просто функцию с переменным числом параметров, а метод класса, то вызов call_user_func_array меняется совсем незначительно.

class Example {
    public function printVars() {
        // ...
    }
}
// ...
$example = new Example();
call_user_func_array(array($example, "printVars"), $valuesArray);

Проведём ещё один эксперимент. Изменим функцию printVars, чтобы она изменяла и возвращала передаваемые ей параметры.

function printVars() {
    $argsArray = func_get_args();

    if (is_array($argsArray)) {
        foreach ($argsArray as $key => $arg) {
            $argsArray[$key] = $arg . '!';
        }
    }
}

$value1 = 'value 1';
$value2 = 'value 2';
$value3 = 'value 3';
$valuesArray = array(&$value1, &$value2, &$value3);

call_user_func_array("printVars", $valuesArray);

var_dump($valuesArray);

Получаем:

array(3) {
[0]=>
&string(7) "value 1"
[1]=>
&string(7) "value 2"
[2]=>
&string(7) "value 3"
}

Хм. Но это же совсем не то, что мы хотели получить! Параметры $value1, $value2 и $value3 не изменились. Оказывается, с помощью func_get_args нельзя получить ссылки на исходные параметры функции. Решается эта проблема с помощью злого хака.

function printVars() {
    $trace = debug_backtrace();
    $argsArray = $trace[1]['args'][1];

    if (is_array($argsArray)) {
        foreach ($argsArray as $key => $arg) {
            $argsArray[$key] = $arg . '!';
        }
    }

}

$value1 = 'value 1';
$value2 = 'value 2';
$value3 = 'value 3';
$valuesArray = array(&$value1, &$value2, &$value3);

call_user_func_array("printVars", $valuesArray);

var_dump($valuesArray);

Вот теперь мы добились нужного результата.

array(3) {
[0]=>
&string(8) "value 1!"
[1]=>
&string(8) "value 2!"
[2]=>
&string(8) "value 3!"
}

Но и это ещё не все проблемы. call_user_func_array возвращает "the function result, or false on error". А если наша функция возвращает false в качестве результата своей работы? Как нам определить, возникала ошибка или нет?

Однозначно определить не получится, но можно хотя бы чуть-чуть улучшить ситуацию при помощи предварительного вызова is_callable.

$handler = "printVars";
if (is_callable($handler)) { 
    $res = call_user_func_array( $handler , $valuesArray );
} else {
    throw new Exception();
}

Теперь мы хотя бы знаем, доступна ли нам вызываемая функция. Может быть, не стоит и пытаться её вызывать.

Третьим вариантом вызова функции с переменным числом параметров является механизм reflection. (Ума не приложу, как корректно перевести термин reflection на русский язык.)
function printVars() {
    $argsCount = func_num_args();
    $argsArray = func_get_args();
    for ($i = 0; $i < $argsCount; $i++) {
        echo $argsArray[$i] . "\n";
    }
}

$valuesArray = array('value 1', 'value 2', 'value 3');

$function_ref = new ReflectionFunction('printVars');
$res = $function_ref->invokeArgs($valuesArray);

Этот код имеет ту же проблему с передачей параметров по ссылке, что и вариант с call_user_func_array, но с возвращением "false" в качестве результата выполнения функции всё хорошо. invokeArgs всегда возвращает результат вызываемой функции, в случае же возникновения ошибки бросается исключение ReflectionException.

А что со скоростью выполнения? Усреднённые результаты двадцати прогонов по 10 000 вызовов одной и той же функции с переменным числом параметров:

  • eval - 0.113432610035 мкс
  • call_user_func_array - 0.0677324056625 мкс
  • reflection - 0.063702750206 мкс

Тесты проводились на PHP 5.3.10. Учитывая сомнительное качество тестового стенда, разностью в скорости работы call_user_func_array и reflection можно смело пренебречь, а вот eval и тут проявил себя хуже всех.

В качестве резюме. Не нужно писать функции с переменным числом параметров. Проблем с ними больше, чем плюсов от их использования, но, если уж пришлось работать с такой функцией, то пробуем использовать в первую очередь механизм reflection. Из имеющихся альтернатив он выглядит наиболее привлекательно.