Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/treetrek.git

Refactors media type sniffer and file

AuthorDave Jarvis <email>
Date2026-02-14 13:22:54 GMT-0800
Commit92f7020a2ac64c6dc829baac336ee1012b252805
Parent7a01dd7
File.php
class File {
- private ?Git $git;
private string $name;
private string $sha;
private string $mode;
private int $timestamp;
private int $size;
private bool $isDir;
+ private string $icon;
- public function __construct( ?Git $git, string $name, string $sha, string $mode, int $timestamp = 0, int $size = 0 ) {
- $this->git = $git;
- $this->name = $name;
- $this->sha = $sha;
- $this->mode = $mode;
- $this->timestamp = $timestamp;
- $this->size = $size;
- $this->isDir = ( $mode === '40000' || $mode === '040000' );
- }
+ private string $mediaType;
+ private string $category;
+ private bool $binary;
- public function compare( File $other ): int {
- if( $this->isDir !== $other->isDir ) {
- return $this->isDir ? -1 : 1;
- }
+ 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';
- return strcasecmp( $this->name, $other->name );
+ $buffer = $this->isDir ? '' : substr($contents, 0, 12);
+
+ $this->mediaType = MediaTypeSniffer::isMediaType($buffer, $name);
+ $this->category = MediaTypeSniffer::isCategory($buffer, $name);
+ $this->binary = MediaTypeSniffer::isBinary($buffer, $name);
+ $this->icon = $this->resolveIcon();
}
- public function render( FileRenderer $renderer ): void {
- $renderer->renderFileItem(
+ public function compare(File $other): int {
+ return $this->isDir !== $other->isDir
+ ? ($this->isDir ? -1 : 1)
+ : strcasecmp($this->name, $other->name);
+ }
+
+ public function render(FileRenderer $renderer): void {
+ $renderer->render(
$this->name,
$this->sha,
$this->mode,
- $this->getIconClass(),
+ $this->icon,
$this->timestamp,
- $this->isDir ? '' : $this->getFormattedSize()
+ $this->size
);
}
- private function getIconClass(): string {
- if( $this->isDir ) return 'fa-folder';
+ public function renderMedia(string $url): bool {
+ $rendered = false;
- return match ( true ) {
- $this->isType( 'application/pdf' ) => 'fa-file-pdf',
- $this->isCategory( MediaTypeSniffer::CAT_ARCHIVE ) => 'fa-file-archive',
- $this->isCategory( MediaTypeSniffer::CAT_IMAGE ) => 'fa-file-image',
- $this->isCategory( MediaTypeSniffer::CAT_AUDIO ) => 'fa-file-audio',
- $this->isCategory( MediaTypeSniffer::CAT_VIDEO ) => 'fa-file-video',
- $this->isCategory( MediaTypeSniffer::CAT_TEXT ) => 'fa-file-code',
- default => 'fa-file',
- };
+ if ($this->isImage()) {
+ echo '<div class="blob-content blob-content-image"><img src="' . $url . '"></div>';
+ $rendered = true;
+ } elseif ($this->isVideo()) {
+ echo '<div class="blob-content blob-content-video"><video controls><source src="' . $url . '" type="' . $this->mediaType . '"></video></div>';
+ $rendered = true;
+ } elseif ($this->isAudio()) {
+ echo '<div class="blob-content blob-content-audio"><audio controls><source src="' . $url . '" type="' . $this->mediaType . '"></audio></div>';
+ $rendered = true;
+ }
+
+ return $rendered;
}
- private function getFormattedSize(): string {
- if( $this->size <= 0 ) return '0 B';
- $units = ['B', 'KB', 'MB', 'GB'];
- $i = (int)floor( log( $this->size, 1024 ) );
- return round( $this->size / pow( 1024, $i ), 1 ) . ' ' . $units[$i];
+ public function renderSize(FileRenderer $renderer): void {
+ $renderer->renderSize($this->size);
}
- public function isType( string $type ): bool {
- return str_contains( MediaTypeSniffer::isMediaType( $this->getSniffBuffer(), $this->name ), $type );
+ public function emitRawHeaders(): void {
+ header("Content-Type: " . $this->mediaType);
+ header("Content-Length: " . $this->size);
+ header("Content-Disposition: inline; filename=\"" . addslashes($this->name) . "\"");
}
- public function isCategory( string $category ): bool {
- return MediaTypeSniffer::isCategory( $this->getSniffBuffer(), $this->name ) === $category;
+ public function isImage(): bool {
+ return $this->category === MediaTypeSniffer::CAT_IMAGE;
}
- public function isBinary(): bool {
- return MediaTypeSniffer::isBinary( $this->getSniffBuffer(), $this->name );
+ public function isVideo(): bool {
+ return $this->category === MediaTypeSniffer::CAT_VIDEO;
}
- private function getSniffBuffer(): string {
- if( $this->isDir ) return '';
+ public function isAudio(): bool {
+ return $this->category === MediaTypeSniffer::CAT_AUDIO;
+ }
- if( $this->git ) {
- return $this->git->peek( $this->sha );
- }
+ public function isText(): bool {
+ return $this->category === MediaTypeSniffer::CAT_TEXT;
+ }
- if( !file_exists( $this->name ) ) return '';
- $handle = @fopen( $this->name, 'rb' );
- if( !$handle ) return '';
- $read = fread( $handle, 12 );
- fclose( $handle );
- return ( $read !== false ) ? $read : '';
+ public function isBinary(): bool {
+ return $this->binary;
+ }
+
+ private function resolveIcon(): string {
+ return $this->isDir
+ ? 'fa-folder'
+ : (str_contains($this->mediaType, 'application/pdf')
+ ? 'fa-file-pdf'
+ : match ($this->category) {
+ MediaTypeSniffer::CAT_ARCHIVE => 'fa-file-archive',
+ MediaTypeSniffer::CAT_IMAGE => 'fa-file-image',
+ MediaTypeSniffer::CAT_AUDIO => 'fa-file-audio',
+ MediaTypeSniffer::CAT_VIDEO => 'fa-file-video',
+ MediaTypeSniffer::CAT_TEXT => 'fa-file-code',
+ default => 'fa-file',
+ });
}
}
git/GitDiff.php
class GitDiff {
- private $git;
+ private Git $git;
private const MAX_DIFF_SIZE = 1048576;
- public function __construct( Git $git ) {
+ public function __construct(Git $git) {
$this->git = $git;
}
-
- public function compare( string $commitHash ) {
- $commitData = $this->git->read( $commitHash );
- $parentHash = '';
- if( preg_match( '/^parent ([0-9a-f]{40})/m', $commitData, $matches ) ) {
- $parentHash = $matches[1];
- }
+ public function compare(string $commitHash) {
+ $commitData = $this->git->read($commitHash);
+ $parentHash = preg_match('/^parent ([0-9a-f]{40})/m', $commitData, $matches) ? $matches[1] : '';
- $newTree = $this->getTreeHash( $commitHash );
- $oldTree = $parentHash ? $this->getTreeHash( $parentHash ) : null;
+ $newTree = $this->getTreeHash($commitHash);
+ $oldTree = $parentHash ? $this->getTreeHash($parentHash) : null;
- return $this->diffTrees( $oldTree, $newTree );
+ return $this->diffTrees($oldTree, $newTree);
}
- private function getTreeHash( $commitSha ) {
- $data = $this->git->read( $commitSha );
- if( preg_match( '/^tree ([0-9a-f]{40})/m', $data, $matches ) ) {
- return $matches[1];
- }
- return null;
+ private function getTreeHash($commitSha) {
+ $data = $this->git->read($commitSha);
+ return preg_match('/^tree ([0-9a-f]{40})/m', $data, $matches) ? $matches[1] : null;
}
- private function diffTrees( $oldTreeSha, $newTreeSha, $path = '' ) {
+ private function diffTrees($oldTreeSha, $newTreeSha, $path = '') {
$changes = [];
-
- if( $oldTreeSha === $newTreeSha ) return [];
- $oldEntries = $oldTreeSha ? $this->parseTree( $oldTreeSha ) : [];
- $newEntries = $newTreeSha ? $this->parseTree( $newTreeSha ) : [];
+ if ($oldTreeSha !== $newTreeSha) {
+ $oldEntries = $oldTreeSha ? $this->parseTree($oldTreeSha) : [];
+ $newEntries = $newTreeSha ? $this->parseTree($newTreeSha) : [];
- $allNames = array_unique( array_merge( array_keys( $oldEntries ), array_keys( $newEntries ) ) );
- sort( $allNames );
+ $allNames = array_unique(array_merge(array_keys($oldEntries), array_keys($newEntries)));
+ sort($allNames);
- foreach( $allNames as $name ) {
- $old = $oldEntries[$name] ?? null;
- $new = $newEntries[$name] ?? null;
- $currentPath = $path ? "$path/$name" : $name;
+ foreach ($allNames as $name) {
+ $old = $oldEntries[$name] ?? null;
+ $new = $newEntries[$name] ?? null;
+ $currentPath = $path ? "$path/$name" : $name;
- if( !$old ) {
- if( $new['is_dir'] ) {
- $changes = array_merge( $changes, $this->diffTrees( null, $new['sha'], $currentPath ) );
- } else {
- $changes[] = $this->createChange( 'A', $currentPath, null, $new['sha'] );
- }
- } elseif( !$new ) {
- if( $old['is_dir'] ) {
- $changes = array_merge( $changes, $this->diffTrees( $old['sha'], null, $currentPath ) );
- } else {
- $changes[] = $this->createChange( 'D', $currentPath, $old['sha'], null );
- }
- } elseif( $old['sha'] !== $new['sha'] ) {
- if( $old['is_dir'] && $new['is_dir'] ) {
- $changes = array_merge( $changes, $this->diffTrees( $old['sha'], $new['sha'], $currentPath ) );
- } elseif( !$old['is_dir'] && !$new['is_dir'] ) {
- $changes[] = $this->createChange( 'M', $currentPath, $old['sha'], $new['sha'] );
+ if (!$old) {
+ $changes = $new['is_dir']
+ ? array_merge($changes, $this->diffTrees(null, $new['sha'], $currentPath))
+ : array_merge($changes, [$this->createChange('A', $currentPath, null, $new['sha'])]);
+ } elseif (!$new) {
+ $changes = $old['is_dir']
+ ? array_merge($changes, $this->diffTrees($old['sha'], null, $currentPath))
+ : array_merge($changes, [$this->createChange('D', $currentPath, $old['sha'], null)]);
+ } elseif ($old['sha'] !== $new['sha']) {
+ $changes = ($old['is_dir'] && $new['is_dir'])
+ ? array_merge($changes, $this->diffTrees($old['sha'], $new['sha'], $currentPath))
+ : (($old['is_dir'] || $new['is_dir'])
+ ? $changes
+ : array_merge($changes, [$this->createChange('M', $currentPath, $old['sha'], $new['sha'])]));
}
}
}
return $changes;
}
- private function parseTree( $sha ) {
- $data = $this->git->read( $sha );
+ private function parseTree($sha) {
+ $data = $this->git->read($sha);
$entries = [];
- $len = strlen( $data );
+ $len = strlen($data);
$pos = 0;
- while( $pos < $len ) {
- $space = strpos( $data, ' ', $pos );
- $null = strpos( $data, "\0", $space );
+ while ($pos < $len) {
+ $space = strpos($data, ' ', $pos);
+ $null = strpos($data, "\0", $space);
- if( $space === false || $null === false ) break;
+ if ($space === false || $null === false) break;
- $mode = substr( $data, $pos, $space - $pos );
- $name = substr( $data, $space + 1, $null - $space - 1 );
- $hash = bin2hex( substr( $data, $null + 1, 20 ) );
+ $mode = substr($data, $pos, $space - $pos);
+ $name = substr($data, $space + 1, $null - $space - 1);
+ $hash = bin2hex(substr($data, $null + 1, 20));
$entries[$name] = [
'mode' => $mode,
'sha' => $hash,
- 'is_dir' => ( $mode === '40000' || $mode === '040000' )
+ 'is_dir' => $mode === '40000' || $mode === '040000'
];
$pos = $null + 21;
}
return $entries;
}
- private function createChange( $type, $path, $oldSha, $newSha ) {
- $oldSize = $oldSha ? $this->git->getObjectSize( $oldSha ) : 0;
- $newSize = $newSha ? $this->git->getObjectSize( $newSha ) : 0;
+ private function createChange($type, $path, $oldSha, $newSha) {
+ $oldSize = $oldSha ? $this->git->getObjectSize($oldSha) : 0;
+ $newSize = $newSha ? $this->git->getObjectSize($newSha) : 0;
+ $result = [];
- if( $oldSize > self::MAX_DIFF_SIZE || $newSize > self::MAX_DIFF_SIZE ) {
- return [
+ if ($oldSize > self::MAX_DIFF_SIZE || $newSize > self::MAX_DIFF_SIZE) {
+ $result = [
'type' => $type,
'path' => $path,
'is_binary' => true,
'hunks' => []
];
- }
-
- $oldContent = $oldSha ? $this->git->read( $oldSha ) : '';
- $newContent = $newSha ? $this->git->read( $newSha ) : '';
-
- $isBinary = false;
+ } else {
+ $oldContent = $oldSha ? $this->git->read($oldSha) : '';
+ $newContent = $newSha ? $this->git->read($newSha) : '';
- if( $newSha ) {
- $f = new VirtualDiffFile( $path, $newContent );
- if( $f->isBinary() ) $isBinary = true;
- }
- if( !$isBinary && $oldSha ) {
- $f = new VirtualDiffFile( $path, $oldContent );
- if( $f->isBinary() ) $isBinary = true;
- }
+ $isBinary = ($newSha && (new VirtualDiffFile($path, $newContent))->isBinary()) ||
+ (!$newSha && $oldSha && (new VirtualDiffFile($path, $oldContent))->isBinary());
- $diff = null;
- if( !$isBinary ) {
- $diff = $this->calculateDiff( $oldContent, $newContent );
+ $result = [
+ 'type' => $type,
+ 'path' => $path,
+ 'is_binary' => $isBinary,
+ 'hunks' => $isBinary ? null : $this->calculateDiff($oldContent, $newContent)
+ ];
}
- return [
- 'type' => $type,
- 'path' => $path,
- 'is_binary' => $isBinary,
- 'hunks' => $diff
- ];
+ return $result;
}
- private function calculateDiff( $old, $new ) {
- $old = str_replace( "\r\n", "\n", $old );
- $new = str_replace( "\r\n", "\n", $new );
+ private function calculateDiff($old, $new) {
+ $old = str_replace("\r\n", "\n", $old);
+ $new = str_replace("\r\n", "\n", $new);
- $oldLines = explode( "\n", $old );
- $newLines = explode( "\n", $new );
+ $oldLines = explode("\n", $old);
+ $newLines = explode("\n", $new);
- $m = count( $oldLines );
- $n = count( $newLines );
+ $m = count($oldLines);
+ $n = count($newLines);
$start = 0;
- while( $start < $m && $start < $n && $oldLines[$start] === $newLines[$start] ) {
+ while ($start < $m && $start < $n && $oldLines[$start] === $newLines[$start]) {
$start++;
}
$end = 0;
- while( $m - $end > $start && $n - $end > $start && $oldLines[$m - 1 - $end] === $newLines[$n - 1 - $end] ) {
+ while ($m - $end > $start && $n - $end > $start && $oldLines[$m - 1 - $end] === $newLines[$n - 1 - $end]) {
$end++;
}
-
- $oldSlice = array_slice( $oldLines, $start, $m - $start - $end );
- $newSlice = array_slice( $newLines, $start, $n - $start - $end );
- $cntOld = count( $oldSlice );
- $cntNew = count( $newSlice );
+ $oldSlice = array_slice($oldLines, $start, $m - $start - $end);
+ $newSlice = array_slice($newLines, $start, $n - $start - $end);
- if( ( $cntOld * $cntNew ) > 500000 ) {
- return [['t' => 'gap']];
- }
+ $result = null;
- $ops = $this->computeLCS( $oldSlice, $newSlice );
+ if ((count($oldSlice) * count($newSlice)) > 500000) {
+ $result = [['t' => 'gap']];
+ } else {
+ $ops = $this->computeLCS($oldSlice, $newSlice);
- $groupedOps = [];
- $bufferDel = [];
- $bufferAdd = [];
+ $groupedOps = [];
+ $bufferDel = [];
+ $bufferAdd = [];
- foreach( $ops as $op ) {
- if( $op['t'] === ' ' ) {
- foreach( $bufferDel as $o ) $groupedOps[] = $o;
- foreach( $bufferAdd as $o ) $groupedOps[] = $o;
- $bufferDel = [];
- $bufferAdd = [];
- $groupedOps[] = $op;
- } elseif( $op['t'] === '-' ) {
- $bufferDel[] = $op;
- } elseif( $op['t'] === '+' ) {
- $bufferAdd[] = $op;
+ foreach ($ops as $op) {
+ if ($op['t'] === ' ') {
+ foreach ($bufferDel as $o) $groupedOps[] = $o;
+ foreach ($bufferAdd as $o) $groupedOps[] = $o;
+ $bufferDel = [];
+ $bufferAdd = [];
+ $groupedOps[] = $op;
+ } elseif ($op['t'] === '-') {
+ $bufferDel[] = $op;
+ } elseif ($op['t'] === '+') {
+ $bufferAdd[] = $op;
+ }
}
- }
- foreach( $bufferDel as $o ) $groupedOps[] = $o;
- foreach( $bufferAdd as $o ) $groupedOps[] = $o;
- $ops = $groupedOps;
+ foreach ($bufferDel as $o) $groupedOps[] = $o;
+ foreach ($bufferAdd as $o) $groupedOps[] = $o;
+ $ops = $groupedOps;
- $stream = [];
+ $stream = [];
- for( $i = 0; $i < $start; $i++ ) {
- $stream[] = ['t' => ' ', 'l' => $oldLines[$i], 'no' => $i + 1, 'nn' => $i + 1];
- }
+ for ($i = 0; $i < $start; $i++) {
+ $stream[] = ['t' => ' ', 'l' => $oldLines[$i], 'no' => $i + 1, 'nn' => $i + 1];
+ }
- $currO = $start + 1;
- $currN = $start + 1;
+ $currO = $start + 1;
+ $currN = $start + 1;
- foreach( $ops as $op ) {
- if( $op['t'] === ' ' ) {
- $stream[] = ['t' => ' ', 'l' => $op['l'], 'no' => $currO++, 'nn' => $currN++];
- } elseif( $op['t'] === '-' ) {
- $stream[] = ['t' => '-', 'l' => $op['l'], 'no' => $currO++, 'nn' => null];
- } elseif( $op['t'] === '+' ) {
- $stream[] = ['t' => '+', 'l' => $op['l'], 'no' => null, 'nn' => $currN++];
+ foreach ($ops as $op) {
+ if ($op['t'] === ' ') {
+ $stream[] = ['t' => ' ', 'l' => $op['l'], 'no' => $currO++, 'nn' => $currN++];
+ } elseif ($op['t'] === '-') {
+ $stream[] = ['t' => '-', 'l' => $op['l'], 'no' => $currO++, 'nn' => null];
+ } elseif ($op['t'] === '+') {
+ $stream[] = ['t' => '+', 'l' => $op['l'], 'no' => null, 'nn' => $currN++];
+ }
}
- }
- for( $i = $m - $end; $i < $m; $i++ ) {
- $stream[] = ['t' => ' ', 'l' => $oldLines[$i], 'no' => $currO++, 'nn' => $currN++];
- }
+ for ($i = $m - $end; $i < $m; $i++) {
+ $stream[] = ['t' => ' ', 'l' => $oldLines[$i], 'no' => $currO++, 'nn' => $currN++];
+ }
- $finalLines = [];
- $lastVisibleIndex = -1;
- $streamLen = count( $stream );
- $contextLines = 3;
+ $finalLines = [];
+ $lastVisibleIndex = -1;
+ $streamLen = count($stream);
+ $contextLines = 3;
- for( $i = 0; $i < $streamLen; $i++ ) {
- $show = false;
+ for ($i = 0; $i < $streamLen; $i++) {
+ $show = false;
- if( $stream[$i]['t'] !== ' ' ) {
- $show = true;
- } else {
- for( $j = 1; $j <= $contextLines; $j++ ) {
- if( ( $i + $j ) < $streamLen && $stream[$i + $j]['t'] !== ' ' ) {
- $show = true;
- break;
- }
- }
- if( !$show ) {
- for( $j = 1; $j <= $contextLines; $j++ ) {
- if( ( $i - $j ) >= 0 && $stream[$i - $j]['t'] !== ' ' ) {
+ if ($stream[$i]['t'] !== ' ') {
+ $show = true;
+ } else {
+ for ($j = 1; $j <= $contextLines; $j++) {
+ if (($i + $j) < $streamLen && $stream[$i + $j]['t'] !== ' ') {
$show = true;
break;
+ }
+ }
+ if (!$show) {
+ for ($j = 1; $j <= $contextLines; $j++) {
+ if (($i - $j) >= 0 && $stream[$i - $j]['t'] !== ' ') {
+ $show = true;
+ break;
+ }
}
}
}
- }
- if( $show ) {
- if( $lastVisibleIndex !== -1 && $i > $lastVisibleIndex + 1 ) {
- $finalLines[] = ['t' => 'gap'];
+ if ($show) {
+ if ($lastVisibleIndex !== -1 && $i > $lastVisibleIndex + 1) {
+ $finalLines[] = ['t' => 'gap'];
+ }
+ $finalLines[] = $stream[$i];
+ $lastVisibleIndex = $i;
}
- $finalLines[] = $stream[$i];
- $lastVisibleIndex = $i;
}
+ $result = $finalLines;
}
- return $finalLines;
+ return $result;
}
- private function computeLCS( $old, $new ) {
- $m = count( $old );
- $n = count( $new );
- $c = array_fill( 0, $m + 1, array_fill( 0, $n + 1, 0 ) );
+ private function computeLCS($old, $new) {
+ $m = count($old);
+ $n = count($new);
+ $c = array_fill(0, $m + 1, array_fill(0, $n + 1, 0));
- for( $i = 1; $i <= $m; $i++ ) {
- for( $j = 1; $j <= $n; $j++ ) {
- if( $old[$i - 1] === $new[$j - 1] ) {
- $c[$i][$j] = $c[$i - 1][$j - 1] + 1;
- } else {
- $c[$i][$j] = max( $c[$i][$j - 1], $c[$i - 1][$j] );
- }
+ for ($i = 1; $i <= $m; $i++) {
+ for ($j = 1; $j <= $n; $j++) {
+ $c[$i][$j] = ($old[$i - 1] === $new[$j - 1])
+ ? $c[$i - 1][$j - 1] + 1
+ : max($c[$i][$j - 1], $c[$i - 1][$j]);
}
}
$diff = [];
$i = $m;
$j = $n;
- while( $i > 0 || $j > 0 ) {
- if( $i > 0 && $j > 0 && $old[$i - 1] === $new[$j - 1] ) {
- array_unshift( $diff, ['t' => ' ', 'l' => $old[$i - 1]] );
+
+ while ($i > 0 || $j > 0) {
+ if ($i > 0 && $j > 0 && $old[$i - 1] === $new[$j - 1]) {
+ array_unshift($diff, ['t' => ' ', 'l' => $old[$i - 1]]);
$i--;
$j--;
- } elseif( $j > 0 && ( $i === 0 || $c[$i][$j - 1] >= $c[$i - 1][$j] ) ) {
- array_unshift( $diff, ['t' => '+', 'l' => $new[$j - 1]] );
+ } elseif ($j > 0 && ($i === 0 || $c[$i][$j - 1] >= $c[$i - 1][$j])) {
+ array_unshift($diff, ['t' => '+', 'l' => $new[$j - 1]]);
$j--;
- } elseif( $i > 0 && ( $j === 0 || $c[$i][$j - 1] < $c[$i - 1][$j] ) ) {
- array_unshift( $diff, ['t' => '-', 'l' => $old[$i - 1]] );
+ } elseif ($i > 0 && ($j === 0 || $c[$i][$j - 1] < $c[$i - 1][$j])) {
+ array_unshift($diff, ['t' => '-', 'l' => $old[$i - 1]]);
$i--;
}
}
return $diff;
}
}
class VirtualDiffFile extends File {
- private $content;
- private $vName;
-
- public function __construct( $name, $content ) {
- parent::__construct( null, $name, '', '100644', 0, strlen( $content ) );
- $this->vName = $name;
- $this->content = $content;
- }
-
- public function isBinary(): bool {
- $buffer = substr( $this->content, 0, 12 );
- return MediaTypeSniffer::isBinary( $buffer, $this->vName );
+ public function __construct(string $name, string $content) {
+ parent::__construct($name, '', '100644', 0, strlen($content), $content);
}
}
pages/FilePage.php
public function render() {
- $this->renderLayout( function() {
- // Use the injected private Git instance
+ $this->renderLayout(function() {
$main = $this->git->getMainBranch();
- if( !$main ) {
+ if (!$main) {
echo '<div class="empty-state"><h3>No branches</h3></div>';
- return;
- }
-
- $target = $this->hash ?: $main['hash'];
- $entries = [];
+ } else {
+ $target = $this->hash ?: $main['hash'];
+ $entries = [];
- // Use the injected private Git instance
- $this->git->walk( $target, function( $file ) use ( &$entries ) {
- $entries[] = $file;
- } );
+ $this->git->walk($target, function($file) use (&$entries) {
+ $entries[] = $file;
+ });
- if( !empty( $entries ) ) {
- $this->renderTree( $main, $target, $entries );
- } else {
- $this->renderBlob( $target );
+ if (!empty($entries)) {
+ $this->renderTree($main, $target, $entries);
+ } else {
+ $this->renderBlob($target);
+ }
}
- }, $this->currentRepo );
+ }, $this->currentRepo);
}
- private function renderTree( $main, $targetHash, $entries ) {
+ private function renderTree($main, $targetHash, $entries) {
$path = $_GET['name'] ?? '';
- $this->renderBreadcrumbs( $targetHash, 'Tree' );
+ $this->renderBreadcrumbs($targetHash, 'Tree');
- echo '<h2>' . htmlspecialchars( $this->currentRepo['name'] ) .
+ echo '<h2>' . htmlspecialchars($this->currentRepo['name']) .
' <span class="branch-badge">' .
- htmlspecialchars( $main['name'] ) . '</span></h2>';
+ htmlspecialchars($main['name']) . '</span></h2>';
- usort( $entries, function( $a, $b ) {
- return $a->compare( $b );
- } );
+ usort($entries, function($a, $b) {
+ return $a->compare($b);
+ });
echo '<div class="file-list">';
- $renderer = new HtmlFileRenderer( $this->currentRepo['safe_name'], $path );
+ $renderer = new HtmlFileRenderer($this->currentRepo['safe_name'], $path);
- foreach($entries as $file) {
- $file->render( $renderer );
+ foreach ($entries as $file) {
+ $file->render($renderer);
}
echo '</div>';
}
-
- private function renderBlob( $targetHash ) {
- $repoParam = '&repo=' . urlencode( $this->currentRepo['safe_name'] );
-
- // Use the injected private Git instance
- $size = $this->git->getObjectSize( $targetHash );
- $buffer = '';
-
- // Use the injected private Git instance
- $this->git->stream( $targetHash, function( $d ) use ( &$buffer ) {
- if( strlen( $buffer ) < 12 ) $buffer .= $d;
- } );
+ private function renderBlob($targetHash) {
+ $repoParam = '&repo=' . urlencode($this->currentRepo['safe_name']);
$filename = $_GET['name'] ?? '';
- $category = MediaTypeSniffer::isCategory( $buffer, $filename );
- $mediaType = MediaTypeSniffer::isMediaType( $buffer, $filename );
+ $file = $this->git->readFile($targetHash, $filename);
+ $size = $this->git->getObjectSize($targetHash);
- $this->renderBreadcrumbs( $targetHash, 'File' );
+ $renderer = new HtmlFileRenderer($this->currentRepo['safe_name']);
- if( $size === 0 ) {
- $this->renderDownloadState( $targetHash, "This file is empty." );
- return;
- }
+ $this->renderBreadcrumbs($targetHash, 'File');
- $rawUrl = '?action=raw&hash=' . $targetHash . $repoParam . '&name=' . urlencode( $filename );
+ if ($size === 0) {
+ $this->renderDownloadState($targetHash, "This file is empty.");
+ } else {
+ $rawUrl = '?action=raw&hash=' . $targetHash . $repoParam . '&name=' . urlencode($filename);
- if( $category === MediaTypeSniffer::CAT_IMAGE ) {
- echo '<div class="blob-content blob-content-image"><img src="' . $rawUrl . '"></div>';
- } elseif( $category === MediaTypeSniffer::CAT_VIDEO ) {
- echo '<div class="blob-content blob-content-video"><video controls><source src="' . $rawUrl . '" type="' . $mediaType . '"></video></div>';
- } elseif( $category === MediaTypeSniffer::CAT_AUDIO ) {
- echo '<div class="blob-content blob-content-audio"><audio controls><source src="' . $rawUrl . '" type="' . $mediaType . '"></audio></div>';
- } elseif( $category === MediaTypeSniffer::CAT_TEXT ) {
- if( $size > 524288 ) {
- $this->renderDownloadState( $targetHash, "File is too large to display (" . $this->formatSize( $size ) . ")." );
- } else {
- $content = '';
- // Use the injected private Git instance
- $this->git->stream( $targetHash, function( $d ) use ( &$content ) { $content .= $d; } );
- echo '<div class="blob-content"><pre class="blob-code">' . htmlspecialchars( $content ) . '</pre></div>';
+ if (!$file->renderMedia($rawUrl)) {
+ if ($file->isText()) {
+ if ($size > 524288) {
+ 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; });
+ echo '<div class="blob-content"><pre class="blob-code">' . htmlspecialchars($content) . '</pre></div>';
+ }
+ } else {
+ $this->renderDownloadState($targetHash, "This is a binary file.");
+ }
}
- } else {
- $this->renderDownloadState( $targetHash, "This is a binary file." );
}
}
- private function renderDownloadState( $hash, $reason ) {
- $url = '?action=raw&hash=' . $hash . '&repo=' . urlencode( $this->currentRepo['safe_name'] );
+ private function renderDownloadState($hash, $reason) {
+ $url = '?action=raw&hash=' . $hash . '&repo=' . urlencode($this->currentRepo['safe_name']);
echo '<div class="empty-state download-state">';
- echo '<p>' . htmlspecialchars( $reason ) . '</p>';
+ echo '<p>' . htmlspecialchars($reason) . '</p>';
echo '<a href="' . $url . '" class="btn-download">Download Raw File</a>';
echo '</div>';
- }
-
- private function formatSize( $size ) {
- if( $size <= 0 ) return '0 B';
-
- $units = ['B', 'KB', 'MB', 'GB'];
- $i = (int)floor( log( $size, 1024 ) );
-
- return round( $size / pow( 1024, $i ), 1 ) . ' ' . $units[$i];
}
- private function renderBreadcrumbs( $hash, $type ) {
- $repoUrl = '?repo=' . urlencode( $this->currentRepo['safe_name'] );
+ private function renderBreadcrumbs($hash, $type) {
+ $repoUrl = '?repo=' . urlencode($this->currentRepo['safe_name']);
$path = $_GET['name'] ?? '';
$crumbs = [
'<a href="?">Repositories</a>',
- '<a href="' . $repoUrl . '">' . htmlspecialchars( $this->currentRepo['name'] ) . '</a>'
+ '<a href="' . $repoUrl . '">' . htmlspecialchars($this->currentRepo['name']) . '</a>'
];
- if ( $path ) {
- $parts = explode( '/', trim( $path, '/' ) );
+ if ($path) {
+ $parts = explode('/', trim($path, '/'));
$acc = '';
- foreach ( $parts as $idx => $part ) {
- $acc .= ( $idx === 0 ? '' : '/' ) . $part;
-
- // The last segment isn't a link
- if ( $idx === count( $parts ) - 1 ) {
- $crumbs[] = htmlspecialchars( $part );
+ foreach ($parts as $idx => $part) {
+ $acc .= ($idx === 0 ? '' : '/') . $part;
+ if ($idx === count($parts) - 1) {
+ $crumbs[] = htmlspecialchars($part);
} else {
- $crumbs[] = '<a href="' . $repoUrl . '&name=' . urlencode( $acc ) . '">' .
- htmlspecialchars( $part ) . '</a>';
+ $crumbs[] = '<a href="' . $repoUrl . '&name=' . urlencode($acc) . '">' .
+ htmlspecialchars($part) . '</a>';
}
}
- } elseif ( $this->hash ) {
- $crumbs[] = $type . ' ' . substr( $hash, 0, 7 );
+ } elseif ($this->hash) {
+ $crumbs[] = $type . ' ' . substr($hash, 0, 7);
}
- echo '<div class="breadcrumb">' . implode( ' / ', $crumbs ) . '</div>';
+ echo '<div class="breadcrumb">' . implode(' / ', $crumbs) . '</div>';
}
}
pages/RawPage.php
private $hash;
- public function __construct( $git, $hash ) {
+ public function __construct($git, $hash) {
$this->git = $git;
$this->hash = $hash;
}
public function render() {
$filename = $_GET['name'] ?? 'file';
- $buffer = '';
-
- $size = $this->git->getObjectSize( $this->hash );
-
- $this->git->stream( $this->hash, function( $d ) use ( &$buffer ) {
- if( strlen( $buffer ) < 12 ) {
- $buffer .= $d;
- }
- } );
- $mediaType = MediaTypeSniffer::isMediaType( $buffer, $filename );
+ $file = $this->git->readFile($this->hash, $filename);
- while( ob_get_level() ) {
+ while (ob_get_level()) {
ob_end_clean();
}
- header( "Content-Type: " . $mediaType );
- header( "Content-Length: " . $size );
- header( "Content-Disposition: inline; filename=\"" . addslashes( $filename ) . "\"" );
+ $file->emitRawHeaders();
- $this->git->stream( $this->hash, function( $d ) {
+ $this->git->stream($this->hash, function($d) {
echo $d;
- } );
+ });
exit;
Delta314 lines added, 353 lines removed, 39-line decrease