Dave Jarvis' Repositories

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

class File {
  private const CAT_IMAGE = 'image';
  private const CAT_VIDEO = 'video';
  private const CAT_AUDIO = 'audio';
  private const CAT_TEXT = 'text';
  private const CAT_ARCHIVE = 'archive';
  private const CAT_BINARY = 'binary';

  private const ARCHIVE_EXTENSIONS = [
    'zip', 'tar', 'gz', '7z', 'rar', 'jar', 'lha', 'bz', 'tgz', 'cab',
    'iso', 'dmg', 'xz', 'z', 'ar', 'war', 'ear', 'pak', 'hqx', 'arj',
    'zoo', 'rpm', 'deb', 'apk'
  ];

  private string $name;
  private string $sha;
  private string $mode;
  private int $timestamp;
  private int $size;
  private bool $isDir;

  private string $mediaType;
  private string $category;
  private bool $binary;

  public function __construct(
    string $name,
    string $sha,
    string $mode,
    int $timestamp,
    int $size,
    string $contents = ''
  ) {
    $this->name = $name;
    $this->sha = $sha;
    $this->mode = $mode;
    $this->timestamp = $timestamp;
    $this->size = $size;
    $this->isDir = $mode === '40000' || $mode === '040000';

    $buffer = $this->isDir ? '' : $contents;

    $this->mediaType = $this->detectMediaType( $buffer );
    $this->category = $this->detectCategory( $name );
    $this->binary = $this->detectBinary();
  }

  public function compare( File $other ): int {
    return $this->isDir !== $other->isDir
      ? ($this->isDir ? -1 : 1)
      : strcasecmp( $this->name, $other->name );
  }

  public function renderListEntry( FileRenderer $renderer ): void {
    $renderer->renderListEntry(
      $this->name,
      $this->sha,
      $this->mode,
      $this->resolveIcon(),
      $this->timestamp,
      $this->size
    );
  }

  public function renderMedia( FileRenderer $renderer, string $url ): bool {
    return $renderer->renderMedia( $this, $url, $this->mediaType );
  }

  public function renderSize( FileRenderer $renderer ): void {
    $renderer->renderSize( $this->size );
  }

  public function highlight( FileRenderer $renderer, string $content ): string {
    return $renderer->highlight( $this->name, $content, $this->mediaType );
  }

  public function isImage(): bool {
    return $this->category === self::CAT_IMAGE;
  }

  public function isVideo(): bool {
    return $this->category === self::CAT_VIDEO;
  }

  public function isAudio(): bool {
    return $this->category === self::CAT_AUDIO;
  }

  public function isText(): bool {
    return $this->category === self::CAT_TEXT;
  }

  public function isBinary(): bool {
    return $this->binary;
  }

  public function isName( string $name ): bool {
    return $this->name === $name;
  }

  public function emitRawHeaders(): void {
    header( "Content-Type: " . $this->mediaType );
    header( "Content-Length: " . $this->size );
    header( "Content-Disposition: attachment; filename=\"" .
      addslashes( basename( $this->name ) ) . "\"" );
  }

  private function resolveIcon(): string {
    return $this->isDir
      ? 'fa-folder'
      : (str_contains( $this->mediaType, 'application/pdf' )
        ? 'fa-file-pdf'
        : match( $this->category ) {
          self::CAT_ARCHIVE => 'fa-file-archive',
          self::CAT_IMAGE => 'fa-file-image',
          self::CAT_AUDIO => 'fa-file-audio',
          self::CAT_VIDEO => 'fa-file-video',
          self::CAT_TEXT => 'fa-file-code',
          default => 'fa-file',
        });
  }

  private function detectMediaType( string $buffer ): string {
    if( $buffer === '' ) return 'application/x-empty';

    $finfo = new finfo( FILEINFO_MIME_TYPE );
    $mediaType = $finfo->buffer( $buffer );

    return $mediaType ?: 'application/octet-stream';
  }

  private function detectCategory( string $filename = '' ): string {
    $parts = explode( '/', $this->mediaType );

    return match( true ) {
      $parts[0] === 'image' => self::CAT_IMAGE,
      $parts[0] === 'video' => self::CAT_VIDEO,
      $parts[0] === 'audio' => self::CAT_AUDIO,
      $parts[0] === 'text' => self::CAT_TEXT,
      $this->isArchiveFile( $filename ) => self::CAT_ARCHIVE,
      str_contains( $this->mediaType, 'compressed' ) => self::CAT_ARCHIVE,
      default => self::CAT_BINARY,
    };
  }

  private function detectBinary(): bool {
    return $this->mediaType !== 'application/x-empty'
        && !str_starts_with( $this->mediaType, 'text/' );
  }

  private function isArchiveFile( string $filename ): bool {
    return in_array(
      strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ),
      self::ARCHIVE_EXTENSIONS,
      true
    );
  }
}