21 августа 2009 г.

Реальный пример применения прогрессбара

Данная тема меня сильно заинтересовала (о чём говорит скорость выхода статей - третья за сутки), поэтому я решил продолжить её и в данной статье описать процесс отображения прогрессбара при копировании файла. Для восприятия статьи Вам необходимо ознакомится с двумя предыдущими: Полоса прогресса и Отображение прогрессбара и параллельное выполнение операции.

Как видно на скриншоте, помимо самого прогрессбара в окне будет отображаться объём завершённости операции (как в процентном виде, так и в размерном), а так же скорость копирования.

Здесь всё просто, стоит только пояснить несколько моментов.

  • sleep(1);
    Перед отображением окна выставлена задержка в одну секунду. Это связано с тем, что в методе start() класса ZendX_Console_Process_Unix имеется аналогичная задержка, как написано в комментарии, дабы избежать проблем. На практике оказалось, что без данных задержек никаких проблем не возникает (и в реальном приложении можно обойтись без них), но тем не менее, они есть. Если же в классе задержку оставить, а из нашей программы убрать, то первую секунду будут показываться совершенно непонятные цифры.

  • Для того, чтобы определить количество завершённости операции мы определяем размер файла функцией filesize(). Хочу заметить, что я использую Linux с файловыми системами ext3 и ext4. В других операционных и файловых системах алгоритмы копирования могут отличаться, и данный способ может не работать.

  • Так как результаты, возвращаемые функцией filesize() кэшируются в рамках одного скрипта, то при каждом вызове update_progressbar() необходимо очистить кэш функцией clearstatcache().

  • Скорость копирования определяется по простому алгоритму - размер уже скопированного файла делится на затраченное время.

  • Для конвертации размера файла из байт в более удобные единицы используется функция conversion_size() из FlightFiles.



Полный код программы:

<?php
 
// Файл с классом ZendX_Console_Process_Unix
include "Unix.php";
 
class MyProcess extends ZendX_Console_Process_Unix
{
/**
* Исходный файл.
* @var string
*/

protected $source;
 
/**
* Файл назначения.
* @var string
*/

protected $dest;
 
/**
* @param string $source Исходный файл
* @param string $dest Файл назначения
*/

public function source_dest($source, $dest)
{
$this->source = $source;
$this->dest = $dest;
}
 
/**
* Выполнение длительной операции в "фоне".
*/

protected function _run()
{
echo "Запуск операции\n";
 
copy($this->source, $this->dest);
 
echo "Операция завершена\n";
}
}
 
$source = '/other/Фильмы/8 миля.mkv';
$dest = '/home/shecspi/8 миля.mkv';
 
$size_source = filesize($source);
$size_source_conv = conversion_size($size_source);
 
$thread = new MyProcess();
$thread->source_dest($source, $dest);
$thread->start();
 
$window = new GtkWindow();
$window->set_size_request(350, 120);
$window->set_position(Gtk::WIN_POS_CENTER);
$window->connect_simple('destroy', array('Gtk', 'main_quit'));
 
$vbox = new GtkVBox();
 
$label = new GtkLabel("Идёт копирование файла.\nПожалуйста, подождите.");
$label->set_justify(Gtk::JUSTIFY_CENTER);
$vbox->pack_start($label, TRUE, TRUE);
 
$normal = new GtkProgressBar();
$normal->set_text('0%');
$vbox->pack_start($normal, FALSE, FALSE);
 
$label_size = new GtkLabel();
$label_size->modify_font(new PangoFontDescription('9'));
$vbox->pack_start($label_size, FALSE, FALSE);
 
$label_speed = new GtkLabel();
$label_speed->modify_font(new PangoFontDescription('9'));
$vbox->pack_start($label_speed, FALSE, FALSE);
 
// sleep() используется для чистоты показаний на первой секунде
sleep(1);
 
list($msec, $sec) = explode(' ', microtime());
$start_time = $sec + $msec;
 
$timeout = Gtk::timeout_add(100, 'update_progressbar', $normal);
 
$window->add($vbox);
$window->show_all();
Gtk::main();
 
/**
* Обновление прогрессбара.
* @param GtkProgressBar $normal Полоса прогресса
* @return bool Для того, чтобы продолжить обновление прогрессбара необходимо вернуть TRUE
*/

function update_progressbar($normal)
{
global $timeout, $thread, $size_source, $dest, $label_size, $size_source_conv, $start_time, $label_speed;
 
// Результат функции filesize() кэшируется,
// поэтому необходим очистить кэш.
clearstatcache();
 
// Размер скопированного файла в байтах
$size_dest = @filesize($dest)."\n";
 
// Размер в процентном выражении
$size_current = round(($size_dest / $size_source) * 100, 2);
 
$normal->set_fraction($size_current / 100);
$normal->set_text($size_current . '%');
 
// Если копирование завершено, останавливаем прогрессбар
if ($size_current == 100)
{
$label_size->set_text('Копирование закончено');
$label_speed->set_text('');
 
Gtk::timeout_remove($timeout);
 
return FALSE;
}
else
{
list($msec, $sec) = explode(' ', microtime());
$current_time = $sec + $msec;
 
// Время, затраченное на выполнение операции
$exec_time = round($current_time - $start_time, 2);
 
$label_size->set_text('Скопировано ' . conversion_size($size_dest) . ' из ' . $size_source_conv);
$label_speed->set_text('Скорость копирования: ' . conversion_size($size_dest / $exec_time) . '/с');
 
return TRUE;
}
}
 
/**
* Переводит размер файла в байтах, указанный в $size_byte,
* в более удобный для восприятия вид.
* @param int $size_byte Размер файла в байтах
* @return string Возвращает размер файла в удобном для восприятия виде.
*/

function conversion_size($size_byte)
{
if ($size_byte >= 0 AND $size_byte < 1024)
{
$size = $size_byte.' Б';
}
elseif ($size_byte >= 1024 AND $size_byte < 1048576)
{
$size = round($size_byte / 1024, 2).' КиБ';
}
elseif ($size_byte >= 1048576 AND $size_byte < 1073741824)
{
$size = round($size_byte / 1048576, 2).' МиБ';
}
elseif ($size_byte >= 1073741824 AND $size_byte < 1099511627776)
{
$size = round($size_byte / 1073741824, 2).' ГиБ';
}
return $size;
}
 
?>

6 комментариев:

zloiia комментирует...

попробовал именно этот код на своей машине... Изменил только файлы, естесственное... И вот что получил. Копирование идет на УРА!. Только шкала так и не отображалась, а проценты были со знаком минус.

zloiia комментирует...

и система загрузилась так, как будто я ее на while(1) завставил число пи считать до миллионного знака после запятой в три потока

Shecspi комментирует...

Отрицательное значение у процентов может быть только, если $size_dest (размер скопированного файла) или $size_source (размер исходного файла) отрицательные. Как известно, функция filesize() для больших файлов (больше 2 ГБ) возвращает неожиданные значения, и они, зачастую, бывают именно отрицательными.

У Вас, по всей видимости, Unix-система, поэтому, если ошибка именно в этом, можете определить размер с помощью команды:
exec('stat -c%s "/other/Фильмы/8 миля.mkv"')
Именно так я и сделал в FlightFiles.

Анонимный комментирует...

извините, я конечно понимаю что большинство тут пользуется Линуксом, но не подскажите 2 вещи:
1) что прописать в PHP.ini что бы подключить GTKHtml или mozembed (у меня при прописывании инклудов в файле или добавлении в php.ini вылетают разного рода ошибки свидетельствующие о не соответствии версий)
2) никто не подскажет компилятор под винду который в состоянии всё это потом скомпилить в exe-шники?

Shecspi комментирует...

1) mozembed у меня подключить не удалось. А вот GTKHtml подключается без проблем (я уже как-то в комментариях описывал процесс установки, поищите). Скачайте с оф. сайта архив с модулями, проблем возникнуть не должно. Хочу заметить, что PHP-GTK не совместим с PHP 5.3.

2) Компилятор я находил, но особо не заморачивался с ним, посмотрите в группе Google, мы там это обсуждали.

LegioNemesis комментирует...

Под Виндовс не удалось запустить GTKHtml, и это распространенная проблема, я думаю разница в версиях библиотек и т.д. Морока того не стоит :-).