Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/treetrek.git
<?php
require_once __DIR__ . '/PackStreamManager.php';
require_once __DIR__ . '/PackLocator.php';
require_once __DIR__ . '/DeltaDecoder.php';
require_once __DIR__ . '/PackEntryReader.php';
require_once __DIR__ . '/PackContext.php';

class GitPacks {
  private const MAX_RAM = 8388608;

  private PackStreamManager $manager;
  private PackLocator       $locator;
  private PackEntryReader   $reader;
  private array             $cacheLoc = [
    'sha'  => '',
    'file' => '',
    'off'  => 0
  ];

  public function __construct( string $objectsPath ) {
    $this->manager = new PackStreamManager();
    $this->locator = new PackLocator(
      $this->manager, $objectsPath
    );
    $this->reader  = new PackEntryReader(
      new DeltaDecoder()
    );
  }

  private function locate(
    string $sha,
    callable $callback
  ): void {
    if( $this->cacheLoc['sha'] === $sha ) {
      $callback(
        $this->cacheLoc['file'],
        $this->cacheLoc['off']
      );
    } else {
      $this->locator->locate(
        $sha,
        function(
          string $packFile,
          int $offset
        ) use ( $sha, $callback ): void {
          $this->cacheLoc = [
            'sha'  => $sha,
            'file' => $packFile,
            'off'  => $offset
          ];

          $callback( $packFile, $offset );
        }
      );
    }
  }

  public function getEntryMeta( string $sha ): array {
    $result = [
      'type'   => 0,
      'size'   => 0,
      'file'   => '',
      'offset' => 0,
    ];

    $this->locate(
      $sha,
      function(
        string $packFile,
        int $offset
      ) use ( &$result ): void {
        $result           = $this->reader->getEntryMeta(
          $this->createContext( $packFile, $offset, 0 )
        );
        $result['file']   = $packFile;
        $result['offset'] = $offset;
      }
    );

    return $result;
  }

  public function peek( string $sha, int $len = 12 ): string {
    $result = '';

    $this->locate(
      $sha,
      function(
        string $packFile,
        int $offset
      ) use ( &$result, $len ): void {
        $result = $this->reader->read(
          $this->createContext( $packFile, $offset, 0 ),
          $len,
          function( string $baseSha, int $cap ): string {
            return $this->peek( $baseSha, $cap );
          }
        );
      }
    );

    return $result;
  }

  public function read( string $sha ): string {
    $result = '';

    $this->locate(
      $sha,
      function(
        string $packFile,
        int $offset
      ) use ( &$result ): void {
        $context = $this->createContext(
          $packFile, $offset, 0
        );
        $size    = $this->reader->getSize( $context );

        $result = $size <= self::MAX_RAM
          ? $this->reader->read(
              $context,
              0,
              function( string $baseSha, int $cap ): string {
                return $cap > 0
                  ? $this->peek( $baseSha, $cap )
                  : $this->read( $baseSha );
              }
            )
          : $result;
      }
    );

    return $result;
  }

  public function stream(
    string $sha,
    callable $callback
  ): bool {
    $result = false;

    foreach( $this->streamGenerator( $sha ) as $chunk ) {
      $callback( $chunk );

      $result = true;
    }

    return $result;
  }

  public function streamGenerator( string $sha ): Generator {
    yield from $this->streamShaGenerator( $sha, 0 );
  }

  public function streamRawCompressed( string $sha ): Generator {
    $found = false;
    $file  = '';
    $off   = 0;

    $this->locate(
      $sha,
      function(
        string $packFile,
        int $offset
      ) use ( &$found, &$file, &$off ): void {
        $found = true;
        $file  = $packFile;
        $off   = $offset;
      }
    );

    yield from $found
      ? $this->reader->streamRawCompressed(
          $this->createContext( $file, $off, 0 )
        )
      : [];
  }

  public function streamRawDelta( string $sha ): Generator {
    $found = false;
    $file  = '';
    $off   = 0;

    $this->locate(
      $sha,
      function(
        string $packFile,
        int $offset
      ) use ( &$found, &$file, &$off ): void {
        $found = true;
        $file  = $packFile;
        $off   = $offset;
      }
    );

    yield from $found
      ? $this->reader->streamRawDelta(
          $this->createContext( $file, $off, 0 )
        )
      : [];
  }

  private function streamShaGenerator(
    string $sha,
    int $depth
  ): Generator {
    $found = false;
    $file  = '';
    $off   = 0;

    $this->locate(
      $sha,
      function(
        string $packFile,
        int $offset
      ) use ( &$found, &$file, &$off ): void {
        $found = true;
        $file  = $packFile;
        $off   = $offset;
      }
    );

    yield from $found
      ? $this->reader->streamEntryGenerator(
          $this->createContext( $file, $off, $depth )
        )
      : [];
  }

  public function getSize( string $sha ): int {
    $result = 0;

    $this->locate(
      $sha,
      function(
        string $packFile,
        int $offset
      ) use ( &$result ): void {
        $result = $this->reader->getSize(
          $this->createContext( $packFile, $offset, 0 )
        );
      }
    );

    return $result;
  }

  private function createContext(
    string $packFile,
    int $offset,
    int $depth
  ): PackContext {
    return new PackContext(
      $this->manager,
      $packFile,
      $offset,
      $depth,
      function( string $baseSha ): int {
        return $this->getSize( $baseSha );
      },
      function(
        string $baseSha,
        int $baseDepth
      ): Generator {
        yield from $this->streamShaGenerator(
          $baseSha, $baseDepth
        );
      }
    );
  }
}