Az ötlet
Az ötlet az volt, hogy magát a fájl olvasást lassítom be, és a használt file resource-t az ftp_fput függvénynek átadva elérem a kívánt hatást. Némi kutatás után arra jutottam, hogy streamwrapper kell nekem. A streamwrapper lényege az, hogy saját protokolt tudunk regisztrálni és kezelni. Bővebben: http://hu2.php.net/manual/en/class.streamwrapper.php Ha például a throttle://
protokollt szeretnénk lekezelni, akkor az elkészített streamwrapper osztályunkat be kell regisztrálni és a stream_open
, stream_close
, stb. metódusokat fogja hívni rendre az fopen
, fclose
, stb.
A limitálást végző osztályban az alábbi főbb műveleteket kell elvégezni:
- Kívánt sávszélesség beállítása
- Fájl megnyitása
- Aktuális sávszélesség kiszámítása
- Fájl olvasás, ha az aktuális sávszélesség a megengedett alatt van, ellenkező esetben egy kis várakozás
A megoldás
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
class StreamWrapper_FileThrottle {
const STREAM_NAME = 'throttle';
const BANDWIDTH = 'bandWidth';
/**
* @var resource
*/
private $fileResource = null;
/**
* @var float
*/
private $startTime = 0;
/**
* @var int
*/
private $fileSize = 0;
/**
* @var resource
*/
public $context;
/**
* kB/s
* 0 means unlimited bandwidth
*
* @var int
*/
private $maxBandWidth = 0;
public function stream_open($path, $mode, $options, &$opened_path) {
$matches = array();
if (!preg_match('#[^:]+:\/\/(.*)#', $path, $matches)) {
throw new InvalidArgumentException("Invalid path '$path'");
}
$path = $matches[1];
$this->fileResource = @fopen($path, $mode);
if ($this->fileResource === false) {
throw new InvalidArgumentException("Invalid path '$path'");
}
$this->fileSize = filesize($path);
$this->startTime = microtime(true);
$contextOptions = stream_context_get_options($this->context);
if ( array_key_exists(self::STREAM_NAME, $contextOptions)
&& array_key_exists(self::BANDWIDTH, $contextOptions[self::STREAM_NAME])) {
$this->maxBandWidth = $contextOptions[self::STREAM_NAME][self::BANDWIDTH];
}
return true;
}
public function stream_close() {
return fclose($this->fileResource);
}
private function getCurrentSpeed() {
$uploadedKBytes = ftell($this->fileResource) / 1024;
$seconds = microtime(true) - $this->startTime;
return $uploadedKBytes / $seconds;
}
public function stream_read($count) {
while ($this->maxBandWidth !== 0 && $this->maxBandWidth < $this->getCurrentSpeed()) {
usleep(100);
}
return fread($this->fileResource, $count);
}
public function stream_write($data) {
throw new Exception("Writting is unsupported on 'throttle' stream");
}
public function stream_tell() {
return ftell($this->fileResource);
}
public function stream_eof() {
return feof($this->fileResource);
}
public function stream_seek($offset, $whence) {
throw new Exception("You can not use 'seek' method on throttle stream");
}
}
|
A sávszélesség határt stream contexten keresztül lehet átadni, melyhez hozzáfér a streamwrapper. A fájl megnyitása és az inicializálási rész a stream_open metódusban találhatók. Az aktuális sávszélességet (ami egész pontosan a fájl megnyitásától számított átlagos sávszélesség) a getCurrentSpeed metódus számolja. Mivel az ftell()-t használja, ezért a seekelés nem engedélyezett (hibás sávszélességet adna vissza).
Használat
Mint már említettem, a streamwrappert be kell regisztrálni, valamint context paraméterben át kell adnunk neki a megengedett sávszélesség értéket.
1
2
3
4
5
6
7
8
|
stream_wrapper_register("throttle", "StreamWrapper_FileThrottle");
$opts = array(
'throttle' => array(
StreamWrapper_FileThrottle::BANDWIDTH => $maxBandwidth
)
);
$fp = fopen('throttle://' . $localFile, 'r', null, stream_context_create($opts));
ftp_fput($ftpResource, $remoteFile, $fp, $mode);
|