Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/treetrek.git
<?php
require_once __DIR__ . '/BasePage.php';
require_once __DIR__ . '/../UrlBuilder.php';
require_once __DIR__ . '/../render/HtmlFileRenderer.php';

class FilePage extends BasePage {
  private const MAX_HIGHLIGHT_SIZE = 65536;
  private const MAX_DISPLAY_SIZE = 524288;

  private $currentRepo;
  private $git;
  private $hash;
  private $path;

  public function __construct(
    array $repositories,
    array $currentRepo,
    Git $git,
    string $hash = 'HEAD',
    string $path = ''
  ) {
    parent::__construct( $repositories );
    $this->currentRepo = $currentRepo;
    $this->git = $git;
    $this->hash = $hash ?: 'HEAD';
    $this->path = $path;
    $this->title = $currentRepo['name'];
  }

  public function render() {
    $this->renderLayout( function() {
      $main = $this->git->getMainBranch();

      if( !$main ) {
        echo '<div class="empty-state"><h3>No branches</h3></div>';
      } else {
        $target  = $this->hash;
        $entries = [];

        $this->git->walk( $target, function( $file ) use ( &$entries ) {
          $entries[] = $file;
        }, $this->path );

        if( !empty( $entries ) && !$this->isExactFileMatch( $entries ) ) {
          $this->renderTree( $main, $target, $entries );
        } else {
          $this->renderBlob( $target );
        }
      }
    }, $this->currentRepo );
  }

  private function isExactFileMatch($entries) {
     return count( $entries ) === 1 &&
            $entries[0]->isName( basename( $this->path ) ) &&
            !$entries[0]->isDir;
  }

  private function renderTree( $main, $targetHash, $entries ) {
    $this->emitBreadcrumbs( $targetHash, 'Tree', $this->path );

    echo '<h2>' . htmlspecialchars( $this->currentRepo['name'] ) .
         ' <span class="branch-badge">' .
         htmlspecialchars( $targetHash ) . '</span></h2>';

    usort( $entries, function( $a, $b ) {
      return $a->compare( $b );
    } );

    echo '<table class="file-list-table">';
    echo '<thead><tr><th></th><th>Name</th><th class="file-mode-cell">Mode</th><th class="file-size-cell">Size</th></tr></thead>';
    echo '<tbody>';

    $renderer = new HtmlFileRenderer(
      $this->currentRepo['safe_name'],
      $this->path,
      $targetHash
    );

    foreach( $entries as $file ) {
      $file->renderListEntry( $renderer );
    }

    echo '</tbody></table>';
  }

  private function renderBlob( $targetHash ) {
    $filename = $this->path;
    $file = $this->git->readFile( $targetHash, $filename );
    $size = $this->git->getObjectSize( $targetHash, $filename );

    $renderer = new HtmlFileRenderer( $this->currentRepo['safe_name'], dirname($filename), $targetHash );

    $this->emitBreadcrumbs( $targetHash, 'File', $filename );

    if( $size === 0 && !$file ) {
       echo '<div class="empty-state">File not found.</div>';
       return;
    }

    $rawUrl = (new UrlBuilder())
      ->withRepo( $this->currentRepo['safe_name'] )
      ->withAction( 'raw' )
      ->withHash( $targetHash )
      ->withName( $filename )
      ->build();

    if( !$file->renderMedia( $renderer, $rawUrl ) ) {
      if( $file->isText() ) {
        if( $size > self::MAX_DISPLAY_SIZE ) {
          ob_start();
          $file->renderSize( $renderer );
          $sizeStr = ob_get_clean();
          $this->renderDownloadState( $targetHash, "File is too large to display ($sizeStr)." );
        } else {
          $content = '';
          $this->git->stream( $targetHash, function( $d ) use ( &$content ) {
            $content .= $d;
          }, $filename );

          echo '<div class="blob-content"><pre class="blob-code">' .
               ($size > self::MAX_HIGHLIGHT_SIZE
                  ? htmlspecialchars( $content )
                  : $file->highlight( $renderer, $content )) .
               '</pre></div>';
        }
      } else {
        $this->renderDownloadState( $targetHash, "This is a binary file." );
      }
    }
  }

  private function renderDownloadState( $hash, $reason ) {
    $url = (new UrlBuilder())
        ->withRepo( $this->currentRepo['safe_name'] )
        ->withAction( 'raw' )
        ->withHash( $hash )
        ->withName( $this->path )
        ->build();

    echo '<div class="empty-state download-state">';
    echo '<p>' . htmlspecialchars( $reason ) . '</p>';
    echo '<a href="' . $url . '" class="btn-download">Download</a>';
    echo '</div>';
  }

  private function emitBreadcrumbs( $hash, $type, $path ) {
    $trail = [];

    if( $path ) {
      $parts = explode( '/', trim( $path, '/' ) );
      $acc = '';

      foreach( $parts as $idx => $part ) {
        $acc .= ($idx === 0 ? '' : '/') . $part;

        if( $idx === count( $parts ) - 1 ) {
          $trail[] = htmlspecialchars( $part );
        } else {
          $url = (new UrlBuilder())
            ->withRepo( $this->currentRepo['safe_name'] )
            ->withAction( 'tree' )
            ->withHash( $hash )
            ->withName( $acc )
            ->build();

          $trail[] = '<a href="' . $url . '">' . htmlspecialchars( $part ) . '</a>';
        }
      }
    } elseif( $hash ) {
      $trail[] = $type . ' ' . substr( $hash, 0, 7 );
    }

    $this->renderBreadcrumbs( $this->currentRepo, $trail );
  }
}