<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 *
 * This trait walks a file a piece at a time. A reader that uses it need
 * not hold the whole file in memory.
 */
namespace seekquarry\yioop\library\av_processing;
/**
 * ByteSource walks a file a piece at a time. A reader that uses it need not
 * hold the whole file in memory.
 */
trait ByteSource
{
    /**
     * $handle stores the open file this reader takes its bytes from. A
     * reader opens
     * the file once and reads pieces of it as it goes, so a large
     * video never sits in memory whole.
     * @var resource
     */
    private $handle;
    /**
     * $file_size stores how many bytes the file holds. A reader checks a
     * chunk's length against it before seeking, so a damaged length cannot send
     * the reader past the end. Set by openSource(). Read by readBytesAt(),
     * sourceSize().
     * @var int
     */
    private int $file_size;
    /**
     * openSource opens the file and remembers its size.
     *
     * @param string $path file to read
     */
    protected function openSource(string $path): void
    {
        $handle = @fopen($path, 'rb');
        if ($handle === false) {
            throw new VideoException("cannot open: $path");
        }
        $this->handle = $handle;
        $size = filesize($path);
        $this->file_size = ($size === false) ? PHP_INT_MAX : $size;
    }
    /**
     * __destruct closes the open file when the reader goes away, so a
     * site that opens many videos does not run out of open files. PHP
     * calls it by itself when nothing holds the reader any longer.
     */
    public function __destruct()
    {
        if (isset($this->handle) && is_resource($this->handle)) {
            fclose($this->handle);
        }
    }
    /**
     * readBytesAt read exactly $length bytes, or fewer if the file ends first.
     *
     * @param int $off how far into the file to read from
     * @param int $length how many bytes to read
     * @return string what was read
     */
    protected function readBytesAt(int $off, int $length): string
    {
        if ($length <= 0 || $off < 0 || $off >= $this->file_size) {
            return '';
        }
        if (fseek($this->handle, $off) !== 0) {
            throw new VideoException("seek failed at $off");
        }
        $bytes = '';
        while (strlen($bytes) < $length) {
            $chunk = fread($this->handle, $length - strlen($bytes));
            if ($chunk === false || $chunk === '') {
                break;
            }
            $bytes .= $chunk;
        }
        return $bytes;
    }
    /**
     * readExact reads the number of bytes asked for and throws where the file
     * ends first. A reader uses it for a field whose length the format fixes,
     * so that a cut short file is reported rather than read as though it held
     * zeros.
     *
     * @param int $off How far into the file to start reading.
     * @param int $length How many bytes must be there.
     * @return string what was read
     */
    protected function readExact(int $off, int $length): string
    {
        $bytes = $this->readBytesAt($off, $length);
        if (strlen($bytes) !== $length) {
            throw new VideoException("short read at $off (wanted $length)");
        }
        return $bytes;
    }
    /**
     * sourceSize works out how many bytes the file holds.
     *
     * @return int what was read
     */
    protected function sourceSize(): int
    {
        return $this->file_size;
    }
}
