Zjištění velikosti obrovského souboru

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Honza Kuchař
Člen | 1662
+
0
-

Ahojte,
narazil jsem asi na omezení PHPka a nevím zatím jak na to. Chtěl jsem přes FileDownloader odeslat soubor větší než 2 GB a nastaly problémy. Zdá se, že PHP prostě nějak normálně neumí zjistit velikost souboru nad 2 GB.

Máte nějaký spolehlivý způsob, jak získat velikost souboru jakékoli velikosti?

Děkuji moc!


A výsledkem této diskuse jsou BigFileTools.

Editoval Honza Kuchař (5. 4. 2011 19:21)

Foowie
Člen | 269
+
0
-

Přinejhorším by mohlo pomoct exec("ls -l $filename | cut -f 5 -d ' '")

voda
Člen | 561
+
0
-

dokumentaci se píše:

Note: Because PHP's integer type is signed and many platforms use 32bit integers, filesize() may return unexpected results for files which are larger than 2GB. For files between 2GB and 4GB in size this can usually be overcome by using sprintf(„%u“, filesize($file)).

jtousek
Člen | 951
+
0
-

Citace z PHP manuálu.

Note: Because PHP's integer type is signed and many platforms use 32bit integers, filesize() may return unexpected results for files which are larger than 2GB. For files between 2GB and 4GB in size this can usually be overcome by using sprintf("%u", filesize($file)).

Jinak na 64bit platformách PHP myslím používá 64bit integer, takže tam by asi problém nebyl.

Další rady najdeš v komentářích uživatelů u dokumentace filesize.

EDIT: Koukám že voda byl rychlejší. :)

Editoval jtousek (30. 3. 2011 23:15)

Honza Kuchař
Člen | 1662
+
0
-

Tam jsem samozrejme patral jako prvni. Ale po nekolika denim patrani po internetu porad nic, co by bylo cross platform… A nepouzivalo by to exec. Toto treba vypada velmi dobre, ale si to funguje jen na linuxu, protoze na windows to cte ty obrovske soubory po bajtu a potom z toho vylezou petabajty. :( (http://www.php.net/…filesize.php#…)

Tedy do 8GB vypada spolehlive toto:
http://www.php.net/…filesize.php#…

Ale jak dale? Obcas potrebuji takto stahnout i soubory kolem 20GB.

A ftell() take umre pri techto velikostech.

Honza Kuchař
Člen | 1662
+
0
-

Foowie: jojo, to je sice fajn, ale to vidim fakt az jako posledni mozne reseni, pokud to fakt jinak nejde.

voda
Člen | 561
+
0
-

Opět to není cross platform, ale možná by šlo na unixu využít fci stat, konkrétně prvek ‚blocks‘ (number of 512-byte blocks allocated).
Jinak místo:

ls -l $filename | cut -f 5 -d ' '

raději: stat -c %s $filename.

Honza Kuchař
Člen | 1662
+
0
-

Stat – stejny problem. Na 2gb selze. :(

voda
Člen | 561
+
0
-

V tom případě bude asi nejjednodušší přejít z 32 bitů na 64 :)

Jinak mi přijde nejjednodušší exec(‚stat‘), ale jak to vyřešit na windows vůbec netuším.

Honza Kuchař
Člen | 1662
+
0
-

Ani 64bitů to prý neřeší. filesize() nefunguje pořád. Ale alspoň funguje ftell().

Ještě k tomu kódu, vzhlem k tomu, že se jedná o doplněk, nerad bych tam volal tyto metody, protože asi byste docela neradi viděli, že by třeba Nette bylo závislé na povoleném execu.

Honza Kuchař
Člen | 1662
+
0
-

A za další, přeinstalovávat server kvůli uploadu souboru je trošku… No…

voda
Člen | 561
+
0
-

Teď jsem zkoušel filesize a stat na 33G velkém souboru a na 64 bitovým systému (Linux) to funguje bez problémů.

Jinak exec(stat) může být druhý stupeň, pokud filesize selže. Samozřejmě s kontrolou, jestli funkce je povolena a pokud ne, tak vyhodit výjimku.

Honza Kuchař
Člen | 1662
+
0
-

Poslal jsem dotaz i sem a zatím bez odpovědi, tak uvidíme.

Honza Kuchař
Člen | 1662
+
0
-

Tak zatim to vypada nasledovne:

  • filesize
  • unsigned filesize
  • exec
  • fread do konce souboru

Vracime float.

Jeste nejake napady jak to udelat?

Milo
Nette Core | 1283
+
0
-

Můj nápad. Chtělo by to ale pořádně ošetřit a otestovat.

function fixSeek($fd, $plusOffset)
{
    $div = floor($plusOffset / PHP_INT_MAX);
    $rem = $plusOffset % PHP_INT_MAX;

    for($i = 0; $i < $div; $i++)
    {
        fseek($fd, PHP_INT_MAX, SEEK_CUR);
    }

    fseek($fd, $rem, SEEK_CUR);
}

function fixFilesize($fileName)
{
    $seekSize = 2.0 * (PHP_INT_MAX + 1);

    $fd = fopen($fileName, 'r');

    $fileSize = filesize($fileName);
    if( $fileSize > 0 )
    {
        fixSeek($fd, $fileSize);
    }

    while( fgetc($fd) !== false ) // TODO: anti loop?
    {
        $fileSize += $seekSize;
        fixSeek($fd, $seekSize - 1); // Minus fgetc() char
    }

    fclose($fd);

    return $fileSize;
}

echo fixFilesize('/home/milo/Avatar.mkv'); // 16017725424

EDIT:
Mám zpětnou vazu, že to nefunguje na WinXP, což jsem i ověřil. fseek() funguje podivně.

Zkusil jsem ale variantu s cUrl a funguje mi na Debianu i WinXP. Cesta k souboru musí být jako stream file:///

Editoval Milo (1. 4. 2011 14:08)

Honza Kuchař
Člen | 1662
+
0
-

Ano, je to tak, toto už jsem také zkoušel. ;) Na Linuxu to funguje, na Windows nikoliv. (ukazuje to nějaké terabajty, na Windows se to chová opravdu nedefinovaně)

Už mám takový nástřel, pak vám to sem dám. Zatím to funguje perfektně. ;)

kravčo
Člen | 721
+
0
-

Trochu som to skúšal a dospel som k tomu, že fseek() na Windowse zlyhá pri posune na pozíciu väčšiu ako PHP_MAX_INT (0x7fffffff pre 32bit).

<?php

$fp = fopen(__DIR__ . '/files/8gb.file', 'rb');

echo fseek($fp, 0x0000ffff, SEEK_SET), "\n"; // 0
echo "\n";
echo fseek($fp, 0x7fffffff, SEEK_SET), "\n"; // 0
echo "\n";
echo fseek($fp, 0x80000000, SEEK_SET), "\n"; // -1
echo "\n";
echo fseek($fp, 0xffffffff, SEEK_SET), "\n"; // -1
echo "\n";
echo fseek($fp, 0x3fffffff, SEEK_SET), "\n"; // 0
echo fseek($fp, 0x3fffffff, SEEK_CUR), "\n"; // 0
echo fseek($fp, 0x3fffffff, SEEK_CUR), "\n"; // -1

WIN PHP 5.3.5nts

Honza Kuchař
Člen | 1662
+
0
-

Přesně tak. ;) Dospěl jsem k tomu samému.

Honza Kuchař
Člen | 1662
+
0
-

Tak nástřel se nachází v distribuci FileDownloaderu, který nyní podporuje stahování souborů větších než 2GB. Zatím však nebude fungovat segmenové stahování na segmetech se „startovní“ pozicí nad 2GB.

Už mám v hlavě řešení, ale bohužel, není to zrovna dvakrát efektivní. Jediné co totiž funguje na posouvání kurzoru je fread(). Ten funguje i na místech mimo PHP_INT_MAX. Netušíte tedy o nějakém efektivním způsobu, jak se přesunout někam dále (bez čtení všech dat v souboru, resp. (velikost dat – PHP_INT_MAX))?

Případně neexistuje nějaká knihovna na práci s velkými soubory? (třeba i PHP extension)

PS: Co zkusit cUrl? :-) Ten vypadá velmi dobře, co se týče podpory velikých souborů…
Nástřel použití curlu, na čtení obrovského souboru, s počátkem čtením nad PHP_INT_MAX

<?php

$writefn = function($ch, $chunk) {
  static $data='';
  static $limit = 500; // 500 bytes, it's only a test

  $len = strlen($data) + strlen($chunk);
  if ($len >= $limit ) {
    $data .= substr($chunk, 0, $limit-strlen($data));
    echo $data;
    return -1;
  }

  $data .= $chunk;
  return strlen($chunk);
};


$path = "trailer1.avi";

$start = PHP_INT_MAX+222112112; // float

$ch = curl_init("file://" . realpath($path));
$range = $start.'-'.($start+500);
curl_setopt($ch, CURLOPT_RANGE, $range);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $writefn);
$curlRet = curl_exec($ch);
if($curlRet === false) {
	echo "cUrl error: ".curl_error($ch);
}

Případně asi jdou nějak číst soubory z bashe, ne? Nemáte nějaké tipy, jak na Windows a jak na Linux?

Honza Kuchař
Člen | 1662
+
0
-

Implementováno ve File Downloaderu. A otestováno že MFU s pluploadem nemá problém s velkými soubory… (>4GB)

Milo
Nette Core | 1283
+
0
-

Hezká implemnetace. Jen chybí jeden return v sizeExec(). // Otherwise, return…

Pro čtení binárních souborů na Linuxu se dá použít dd nebo od, na Win nevim.

Honza Kuchař
Člen | 1662
+
0
-

Trošku jsem to poupravit, teď už je to logičtější…

Milo
Nette Core | 1283
+
0
-

Trochu jsem testoval. Na WinXP 32bit, PHP 5.2.6, Apache 2, PHP jako modul vrací správné velikosti pro soubory o velikosti

PHP_INT_MAX			nativeSeek, cUrl, sizeExec, sizeCom, sizeNativeRead
PHP_INT_MAX + 1			nativeSeek, cUrl, sizeExec, sizeCom, sizeNativeRead
PHP_INT_MAX + PHP_INT_MAX + 2	cUrl, sizeExec, sizeCom, sizeNativeRead
PHP_INT_MAX + PHP_INT_MAX + 3	cUrl, sizeExec, sizeCom, sizeNativeRead
16017725424 (cca. 16GB)		cUrl, sizeExec, sizeCom, sizeNativeRead

kde PHP_INT_MAX je 2147483647, tj. 0x7fffffff

A subjektivně mi přijde, že sizeCom je několikrát rychlejší než sizeExec.

Jen pro soubor s nulovou velikostí vyhodí InvalidStateException.

Honza Kuchař
Člen | 1662
+
0
-

Také si myslím, že to tak je. PS: Když jsi to testoval nechceš přidat volání Debug::timer();, který ti vrátí, jak dlouho jednotlivá volání trvala? Udělám já.

Honza Kuchař
Člen | 1662
+
0
-

Výsledek profilování (seřazeno od nejrychlejšího v sekundách)

sizeCurl	0.00045299530029297
sizeNativeSeek	0.00052094459533691
sizeCom		0.0031449794769287
sizeExec	0.042937040328979
sizeNativeRead	2.7670161724091
Milo
Nette Core | 1283
+
0
-

Tak to je ovšem vtipné, že cUrl bylo rychlejší než nativeSeek :)

Honza Kuchař
Člen | 1662
+
0
-

Ale je to tak, předpokládám, že Nette\Debug::timer() nelže.

Honza Kuchař
Člen | 1662
+
0
-

BigFileTools v doplňcích
→ Zde se nachází konec vlákna. :)

Pokračování je zde. Do tohoto vlána prosím už nic nepište.