вторник, 20 января 2015 г.

Android. How to change action bar title color programmatically

Если Вам необходимо программно поменять цвет заголовка у ActionBar, но мир взбунтовался против Вас и рекомендует делать это разметкой, то добро пожаловать сюда.

Сделать это проще простого.

Заголовок у ActionBar может быть задан строкой из ресурсов, строкой Java или экземпляром класса Spannable.

В Android есть класс SpannableString, наследник Spannable, который позволяет описать разметку и оформление строки.

Воспользуемся им и создадим заголовок необходимого цвета:

String titleText = "Заголовок";
Spannable title = new SpannableString(titleText);
title.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.black)), 0, title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
actionBar.setTitle(title);

Если смена цвета заголовка связана с меню, добавьте приведенный выше код в метод onCreateOptionsMenu у Activity.

понедельник, 19 января 2015 г.

Android. Save fragment after screen rotation

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

О пользователях нужно заботиться и сохранять введенные значения в формах после поворота экрана.

Причина


Activity во время своей жизни может множество раз менять свое состояние, вплоть до уничтожения и пересоздания. Это как раз и происходит во время поворота экрана. Activity уничтожается и создается снова.

Решение в лоб


Эта проблема довольно легко решается с помощью методов onSaveInstanceState и onRestoreInstanceState у Activity. В первом методе можно сохранить данные в виде пары Ключ/Значение, а во втором восстановить их. Эти данные будут сохранены после уничтожения Activity.

По данной теме можно найти множество материалов.

Однако, этот метод неуместен, если необходимо сохранять большое число данных.

Решение при использовании Fragments


Если форма с полями ввода находится во фрагменте, можно просто напросто сохранить фрагмент в памяти, и снова задействовать его после поворота экрана. Однако, нужно иметь ввиду, что фрагмент в любом случае пересоздаст свое представление, что необходимо обработать.

Первым делом нужно сообщить фрагменту, чтобы его экземпляр сохранился после пересоздания Activity. В конструкторе фрагмента добавьте строку:

this.setRetainInstance(true);

После этого скорректируйте код метода onCreate у Activity, чтобы восстановить сохраненный после пересоздания фрагмент:

private static String FRAGMENT_INSTANCE_NAME = "fragment";
MyFragment fragment = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.my_activity);

    // Восстанавливаем уже созданный фрагмент
    FragmentManager fm = getSupportFragmentManager();
    fragment = (MyFragment) fm.findFragmentByTag(FRAGMENT_INSTANCE_NAME);
    // Если фрагмент не сохранен, создаем новый экземпляр
    if(fragment == null){
        fragment = new MyFragment();
        fm.beginTransaction().add(R.id.container, fragment, FRAGMENT_INSTANCE_NAME).commit();
    }
}

Теперь фрагмент не пересоздается после поворота экрана, однако его представление все равно создается заново, так как вызывается метод onCreateView. Стандартного способа предотвратить вызов этого метода я не нашел, так что, нужно включить несколько дополнительных проверок в этот метод:

View view = null;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // Проверяем, создано ли представление фрагмента
    if(view == null) {
        // Если представления нет, создаем его
        view = inflater.inflate(R.layout.my_fragment, container, false);
    } else {
        // Если представление есть, удаляем его из разметки,
        // иначе возникнет ошибка при его добавлении
        ((ViewGroup) view.getParent()).removeView(view);
    }
    return view;
}

Все готово. При повороте экрана Activity использует старый экземпляр фрагмента, представление которого так же не пересоздается. Это позволяет сохранить все введенные в форме данные.

Внимание


Если вы используете адаптеры, то при повороте экрана они автоматически уничтожат и создадут заново каждый элемент, за который они отвечают. Это произойдет с вызовом метода getView.



среда, 14 января 2015 г.

Linux. Resources (Memory, CPU, swap) monitoring with notifications

Нехватка ресурсов на сервере (RAM, место на жестком диске, загрузка CPU), может привести к плохим последствиям. Чтобы быстро узнать об этом и принять меры, можно настроить мониторинг ресурсов и оповещение в случае опасности.

Для этого существует множество сервисов, которые можно найти в сети.
В этой статье описано, как настроить мониторинг системы через сервис monit (http://mmonit.com/monit/), а именно:
1. Мониторинг загруженности CPU, swap и оперативной памяти.
2. Оповещение на email в случае достижения загрузки предельного значения.

Краткое описание




Monit - легковесная система мониторинга серверов. Monit устанавливается на сервер и обеспечивает возможность отправки уведомлений в случае обнаружения проблем. Кроме того, Monit может автономно выполнять определенное действие в качестве реакции на заданные события.

Программа имеет следующую функциональность:
  • Отслеживание состояния серверов (доступность, потребление ресурсов).
  • Мониторинг демонов (состояние, потребляемые ресурсы, количество child-process и многое другое).
  • Мониторинг сетевых сервисов (возможность подключения и корректность ответа).
  • Выполнение встроенных или собственных (с помощью скриптов) действий при достижении определенных событий.
  • Отправка уведомлений на Email или в централизованный web-интерфейс M\Monit.

Поддерживаются ОС GNU\LinuxFreeBSDOpenBSDSolarisMac OS XAIX.

Установка


Здесь все проще простого. Выполняем:
sudo apt-get install monit

Доступ к настройкам


Настройка поведения monit осуществляется через консоль или файл настроек. Опции, заданные через консоль, перегружают настройки из файла.

Настройки содержатся в файле monitrc, который, по умолчанию, должен находиться в ~/.monitrc.

Однако, после установки, он находился в /etc/monit/monitrc.

Чтобы проверить файл на синтаксические ошибки, выполните команду:
monit -t

Структура файла настроек


Файл настроек включает в себя следующие категории:
1. Глобальные значения (Global Section)
2. Сервисы (Services)
3. Подключаемые модули (Includes)

Глобальные значения, это опции поведения программы (Период проверки, путь к лог файлу, список адресов для рассылки и т.д.).

Они задаются следующим образом:
set option_name parameters

Некоторые значения заданы в файле по умолчанию, например:
set daemon 120 

Значение 120 означает, что демон будет запускать проверку системы каждые 120 секунд.

Сервисы, это то, что monit будет делать во время проверки (Проверить состояние процессора, памяти, какого-либо процесса и т.д.)

Они задаются следующим образом:
check category parameters

Подключаемые модули позволяют разделить файл настроек на несколько файлов. По умолчанию в файле настроек есть строка:
include /etc/monit/conf.d/*

Настройка


И так, откройте файл настроек для редактирования:
vim /etc/monit/monitrc

Ниже приведен список опций для решения поставленной задачи.
Добавьте недостающие опции в файл настроек.
# Периодичность проверки в секундах
set daemon 120
# Путь к лог файлу. Оставил без изменений
set logfile /var/log/monit.log
# Путь к уникальному идентификатору. Оставил без изменений
set idfile /var/lib/monit/id
# Путь к состоянию. Оставил без изменений
set statefile /var/lib/monit/state
# Email адрес, с которого производить рассылку оповещений
set mailserver smtp.gmail.com port 587
    username "EmailAccount" password "email_password"
    using tlsv1
# Очередь событий. Оставил без изменений
set eventqueue
      basedir /var/lib/monit/events
      slots 100
# Адреса, куда присылать уведомления
set alert 4ybakut2004@gmail.com
set alert ExtraEmail@gmail.com

Так же есть возможность настроить web интерфейс, но в рамках решаемой задачи это не нужно.

Далее приведен сервис для проверки состояния CPU, swap и RAM.
Пропишите его в файле настроек.
check system ServiceName
      if memory usage > 10% then alert
      if swap usage > 25% then alert
      if cpu usage (user) > 70% then alert
      if cpu usage (system) > 30% then alert
      if cpu usage (wait) > 20% then alert

Здесь ServiceName - любое имя. Оно будет использоваться в email уведомлениях.
system - Категория проверки. Каждая категория имеет собственный набор настроек, которые можно посмотреть на официальной странице. В данном случае мониторится система.

Синтаксис довольно понятный и не нуждается в объяснении. 
Всякий раз, когда возникает alert, monit записывает данные об этом в лог, а так же отправляет email на указанные адреса.

Запуск


Чтобы запустить monit, выполните команду:
sudo monit

При этом запустится демон, выполняющий все описанные сервисы.
Если напечатать команду еще раз, сервисы запустятся немедленно вне очереди.

Чтобы прекратить мониторинг, выполните:
sudo monit quit

Полный список опций программы доступен здесь:

четверг, 8 января 2015 г.

Backups in Ruby On Rails. Gem Backup

Каждый Ruby On Rails программист, опасающийся за сохранность своих зубов, должен задуматься о настройке резервного копирования приложения и базы данных (Backup copy, далее - бэкап).

Здесь описан процесс настройки резервного копирования Ruby On Rails приложений с помощью гема Backup (https://github.com/meskyanichi/backup).

В результате будет настроено следующее:
1. Сохранение в архив всех файлов приложения и инструкций для восстановления БД один раз в сутки;
2. Отправка архива на FTP сервер;
3. Оповещение по email о результате бэкапа.

Установка


Добавьте в gemfile строку:
gem 'backup'

Выполните
bundle install

Создание модели бэкапа


Модель бэкапа описывает, каким образом делать резервное копирование (Какие файлы и базы данных сохранять, где их сохранять, каким образом сжимать файлы, как оповещать о проделанной работе и т.д.)

Создайте простейшую модель бэкапа:
backup generate:model --trigger my_backup --archives --storages='local' --compressors='gzip' --notifiers='mail' --databases='postgresql'

--trigger - Имя триггера для модели бэкапа
--archives - Модель будет использовать tar архиватор
--storages - Места хранения бэкапов (локальное хранилище, облачное хранилище, ftp)
--compressors - (bzip2, custom, gzip)
--notifiers - способы оповещения, например, через email
--databases - используемые базы данных (в данном случае - postgresql)

Полный список опций для бэкап генератора можно посмотреть здесь:
Данная команда создаст файл модели в зависимости от переданных параметров, а так же, конфигурационный файл.

После выполнения команды должны появиться строки:
Generated model file: '/home/username/Backup/models/my_backup.rb'.
Generated configuration file: '/home/username/Backup/config.rb'.

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

Настройка модели бэкапа


Стандартно созданный файл модели имел следующий вид (комментарии упущены):
Backup::Model.new(:my_backup, 'Description for my_backup') do
  split_into_chunks_of 250
  
  archive :my_archive do |archive|
    archive.add "/path/to/a/file.rb"
    archive.add "/path/to/a/folder/"
    archive.exclude "/path/to/a/excluded_file.rb"
    archive.exclude "/path/to/a/excluded_folder"
  end

  database PostgreSQL do |db|
    db.name               = "my_database_name"
    db.username           = "my_username"
    db.password           = "my_password"
    db.host               = "localhost"
    db.port               = 5432
    db.socket             = "/tmp/pg.sock"
    db.skip_tables        = ["skip", "these", "tables"]
    db.only_tables        = ["only", "these", "tables"]
    db.additional_options = ["-xc", "-E=utf8"]
  end

  store_with Local do |local|
    local.path       = "~/backups/"
    local.keep       = 5
  end

  compress_with Gzip

  notify_by Mail do |mail|
    mail.on_success           = true
    mail.on_warning           = true
    mail.on_failure           = true

    mail.from                 = "sender@email.com"
    mail.to                   = "receiver@email.com"
    mail.address              = "smtp.gmail.com"
    mail.port                 = 587
    mail.domain               = "your.host.name"
    mail.user_name            = "sender@email.com"
    mail.password             = "my_password"
    mail.authentication       = "plain"
    mail.encryption           = :starttls
  end

end

Файл нужно доработать, а именно:

1. Указать, какие каталоги нужно добавить в бэкап, а какие исключить:
archive :mayak_archive do |archive|
    archive.add "/home/username/Workspace/RubyOnRails/Appname"
end

Все предельно просто. Указываем папки и файлы, которые нужно добавить в архив с помощью archive.add.
Для исключения каталога из архива используйте archive.exclude.

2. Настроить доступ к БД:
database PostgreSQL do |db|
    db.name               = "my_database_name"
    db.username           = "my_username"
    db.password           = "my_password"
    db.host               = "localhost"
    db.port               = 5432
    db.socket             = "/var/run/postgresql"
    db.additional_options = []
end

Указываем имя базы данных, пользователя БД с паролем, адрес и порт для доступа к БД и сокет БД.

Путь к сокету БД у меня отличался от стандартного. Я указал свой: /var/run/postgresql.
Чтобы выполнить бэкап БД на удаленной машине, вместо localhost нужно указать ip адрес сервера.

Если вы хотите сохранять лишь некоторые таблицы, используйте db.skip_tables и db.only_tables.

3. Настроить оповещение по email:
Я использовал почтовый ящик gmail. Конфигурация приведена именно для него.
notify_by Mail do |mail|
    mail.on_success           = true
    mail.on_warning           = true
    mail.on_failure           = true

    mail.from                 = "my_email@gmail.com"
    mail.to                   = "4ybakut2004@gmail.com"
    mail.address              = "smtp.gmail.com"
    mail.port                 = 587
    mail.domain               = "gmail.com"
    mail.user_name            = "my_email@gmail.com"
    mail.password             = "my_password"
    mail.authentication       = "plain"
    mail.encryption           = :starttls
end

Вместо my_email@gmail.com укажите адрес, с которого будет происходить рассылка.
Вместо my_password укажите пароль к этому почтовому ящику.

4. Настроить отправление архива на FTP:
store_with FTP do |server|
    server.username     = 'ftp_username'
    server.password     = 'ftp_password'
    server.ip           = 'ftp_ip_address'
    server.port         = 21
    server.path         = '/путь/сохранения/на/ftp/сервере'
    server.keep         = 4
    server.passive_mode = false
end

Указываем данные для доступа к FTP (логин, пароль, ip адрес), путь для сохранения бэкапов и количество хранимых бэкапов.

Настройка завершена. Полный вид файла модели:
Backup::Model.new(:my_backup, 'Description for my_backup') do
  split_into_chunks_of 250

  archive :mayak_archive do |archive|
    archive.add "/home/username/Workspace/RubyOnRails/Appname"
  end

  database PostgreSQL do |db|
    db.name               = "my_database_name"
    db.username           = "my_username"
    db.password           = "my_password"
    db.host               = "localhost"
    db.port               = 5432
    db.socket             = "/var/run/postgresql"
    db.additional_options = []
  end

  store_with Local do |local|
    local.path       = "~/backups/"
    local.keep       = 5
  end

  store_with FTP do |server|
    server.username     = 'ftp_username'
    server.password     = 'ftp_password'
    server.ip           = 'ftp_ip_address'
    server.port         = 21
    server.path         = '/путь/сохранения/на/ftp/сервере'
    server.keep         = 4
    server.passive_mode = false
  end

  compress_with Gzip

  notify_by Mail do |mail|
    mail.on_success           = true
    mail.on_warning           = true
    mail.on_failure           = true

    mail.from                 = "my_email@gmail.com"
    mail.to                   = "4ybakut2004@gmail.com"
    mail.address              = "smtp.gmail.com"
    mail.port                 = 587
    mail.domain               = "gmail.com"
    mail.user_name            = "my_email@gmail.com"
    mail.password             = "my_password"
    mail.authentication       = "plain"
    mail.encryption           = :starttls
  end

end

Запуск бэкапа


Чтобы запустить бэкап выполните команду:
bundle exec backup perform --trigger my_backup

Имя триггера укажите то, которое вы задали во время создания модели.

После этого будет создан архив приложения и БД, который поместится по указанному адресу на FTP сервере. Письмо с результатом будет отправлено на указанный почтовый ящик.

Периодическое выполнения бэкапа


Для выполнения бэкапа по расписанию можно создать cron job. Для этого я использовал гем whenever (https://github.com/javan/whenever), позволяющий легко организовывать периодические операции в rails приложениях через cron jobs.

Однако, для выполнения бэкапа на сервере, команду пришлось доработать и указать путь к gemfile:
BUNDLE_GEMFILE=/path/to/gemfile/Gemfile  bundle exec backup perform --trigger my_backup


Восстановление БД


Для чего был весь этот геморрой? Для того, чтобы восстановить базу данных в случае ее порчи/уничтожения.

Чтобы восстановить БД из бэкапа, найдите файла дампа в архиве (он будет иметь примерно такое имя - PostgreSQL.sql) и выполните команду:

psql имя_БД < файл_дампа

Подробное описание здесь
И на русском, здесь:

Rails. Can not allocate momory - nodejs

Deployment в Rails, это процесс, требующий достаточно большого количества системных ресурсов. Если в вашей системе, например, мало оперативной памяти, то могут возникнуть проблемы при разворачивании приложения.

Зачастую это происходит во время прекомпиляции ресурсов, которой необходимо много ОЗУ.
При этом выдается данная, или похожая на нее ошибка:

Can not allocate momory - nodejs

Есть несколько вариантов решения этой проблемы:
1. Увеличить объем ОЗУ
2. Добавить swap файл в системе
3. Придумайте свой

Объяснять первый, думаю, не стоит.
А как добавить swap файл, смотрите здесь:
http://4ybakut2004.blogspot.ru/2014/10/how-to-add-swap-on-ubuntu.html