Многие интернет-проекты (неважно, что это: движки форумов, фотогалереи или что-то ещё) позволяют пользователям загружать файлы на сайт и скачивать их оттуда. С закачкой проблем не возникает никогда. А вот со скачиванием закачанных файлов зачастую бывает одна маленькая проблема: многие вебмастера хотят знать, сколько раз был скачан тот или иной файл. Что же они делают? Очень просто: дают ссылку не на собственно файл, а на некоторый скрипт, который выглядит примерно так: <?php // тут подсчёт скачиваний, разные действия // а дальше: просто выдача всего содержимого файла, примерно так: readfile ($filename);?> И всё бы ничего, но: Файлы порой бывают очень большими, а связь — очень некачественной. Ну и что? А очень просто: выданный таким образом файл нельзя скачивать порциями. То есть ни один менеджер скачиваний (например, ReGet, FlashGet или Download Master и прочие) не смогут: Скачивать файл в несколько потоков; Приостановить скачивание в любой момент, а через некоторый промежуток времени начать скачивать файл с места остановки (а ведь в этом и суть докачки). Что делать? Вот и меня посетила такая мысль. Полчаса экспериментов — и у меня получилась очень хорошая функция. Отдаю её Вам: function downloadFile($filename, $mimetype='application/octet-stream') { if (!file_exists($filename)) die('Файл не найден'); $from=$to=0; $cr=NULL; if (isset($_SERVER['HTTP_RANGE'])) { $range=substr($_SERVER['HTTP_RANGE'], strpos($_SERVER['HTTP_RANGE'], '=')+1); $from=strtok($range, '-'); $to=strtok('/'); if ($to>0) $to++; if ($to) $to-=$from; header('HTTP/1.1 206 Partial Content'); $cr='Content-Range: bytes ' . $from . '-' . (($to)?($to . '/' . $to+1):filesize($filename)); } else header('HTTP/1.1 200 Ok'); $etag=md5($filename); $etag=substr($etag, 0, 8) . '-' . substr($etag, 8, 7) . '-' . substr($etag, 15, 8); header('ETag: "' . $etag . '"'); header('Accept-Ranges: bytes'); header('Content-Length: ' . (filesize($filename)-$to+$from)); if ($cr) header($cr); header('Connection: close'); header('Content-Type: ' . $mimetype); header('Last-Modified: ' . gmdate('r', filemtime($filename))); $f=fopen($filename, 'r'); header('Content-Disposition: attachment; filename="' . basename($filename) . '";'); if ($from) fseek($f, $from, SEEK_SET); if (!isset($to) or empty($to)) { $size=filesize($filename)-$from; } else { $size=$to; } $downloaded=0; while(!feof($f) and !connection_status() and ($downloaded<$size)) { echo fread($f, 512000); $downloaded+=512000; flush(); } fclose($f);} Функция принимает два параметра: $filename — полный путь до файла, $mimetype — MIME-тип файла (если не знаете, что это такое — не указывайте второй параметр при вызове функции). Вызов функции может быть например таким: downloadFile('file/archive.zip', 'application/zip'); // Выдаём пользователю файл "file/archive.zip" и указываем MIME-тип Или таким: downloadFile('i_want_to_break_free.mp3', 'audio/mpeg'); // Выдаём файл "i_want_to_break_free.mp3" и снова указываем MIME-тип А можно и вот так: downloadFile('somefile.ext'); // Здесь пользователю отдаётся файл "somefile.ext", но MIME-тип не указывается. // Впрочем, ничего страшного в этом нет. Однако не забудьте вот о чём: функция посылает заголовки header. Значит до вызова функции не должен выводиться никакой текст. После вызова функции лучше тоже ничего не выводить — некоторые менеджеры закачек не смотрят на то, что необходимые размеры скачаны и качают пока не закончится поток данных. А это приведёт к тому, что в нормальный файл попадёт всякий бред. Я бы посоветовал сразу после вызова downloadFile() сделать вызов die(); Глупо было бы думать, что я один такой умный (хотя и хотелось бы) . Но Вы можете (вместо моей функции) использовать PEAR-библиотеку HTTP_Download, которая реализует всё то, что и моя функция, и много других возможностей (как-то: кэширование, сжатие данных «на лету» и прочее).
|