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 {
        $context = $this->createContext(
          $packFile, $offset, 0
        );
        $meta    = $this->reader->getEntryMeta(
          $context
        );

        $result           = $meta;
        $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 {
        $context = $this->createContext(
          $packFile, $offset, 0
        );
        $result  = $this->reader->read(
          $context,
          $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 );

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

    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;
      }
    );

    if( $found ) {
      $context = $this->createContext(
        $file, $off, 0
      );

      yield from $this->reader->streamRawCompressed(
        $context
      );
    }
  }

  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;
      }
    );

    if( $found ) {
      $context = $this->createContext(
        $file, $off, 0
      );

      yield from $this->reader->streamRawDelta(
        $context
      );
    }
  }

  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;
      }
    );

    if( $found ) {
      $context = $this->createContext(
        $file, $off, $depth
      );

      yield from $this->reader->streamEntryGenerator(
        $context
      );
    }
  }

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

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

    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
        );
      }
    );
  }
}