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, $currentRepo['name'] );

    $this->currentRepo = $currentRepo;
    $this->git         = $git;
    $this->hash        = $hash ?: 'HEAD';
    $this->path        = $path;
  }

  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>';
    } else {
      $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 );
  }
}