tapis_job-1.4.1-alpha1/src/Controller/JobOutputController.php

src/Controller/JobOutputController.php
<?php

namespace Drupal\tapis_job\Controller;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Url;
use Drupal\tapis_job\TapisJobInterface;
use Drupal\tapis_job\TapisProvider\TapisJobProviderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
 * Class JobOutputController.
 *
 * This class is used to create a controller that
 * allows a user to download a job's output file.
 *
 * @package Drupal\tapis_job\Controller
 */
class JobOutputController extends ControllerBase {

  /**
   * The Tapis Job provider.
   *
   * @var \Drupal\tapis_job\TapisProvider\TapisJobProviderInterface
   */

  protected TapisJobProviderInterface $tapisJobProvider;

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected RequestStack $requestStack;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Constructs a JobOutputController object.
   *
   * @param \Drupal\tapis_job\TapisProvider\TapisJobProviderInterface $tapisJobProvider
   *   The Tapis Job provider.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The configuration interface.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler interface.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   */
  public function __construct(TapisJobProviderInterface $tapisJobProvider,
                              RequestStack $request_stack,
                              ConfigFactoryInterface $configFactory,
                              ModuleHandlerInterface $module_handler,
                              EntityTypeManagerInterface $entityTypeManager) {
    $this->tapisJobProvider = $tapisJobProvider;
    $this->requestStack = $request_stack;
    $this->configFactory = $configFactory;
    $this->moduleHandler = $module_handler;
    $this->entityTypeManager = $entityTypeManager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('tapis_job.tapis_job_provider'),
      $container->get('request_stack'),
      $container->get('config.factory'),
      $container->get('module_handler'),
      $container->get('entity_type.manager'),
    );
  }

  /**
   * Download a job's output file.
   *
   * @param \Drupal\tapis_job\TapisJobInterface|null $tapis_job
   *   The Tapis Job.
   *
   * @return \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\StreamedResponse|void
   *   return the job output file.
   */
  public function getJobOutputFile(TapisJobInterface $tapis_job = NULL) {
    if (!$this->currentUser()->hasPermission("create job")) {
      return new RedirectResponse('/system/403');
    }

    if ($tapis_job) {

      $tenantId = $tapis_job->getTenantId();
      $request = $this->requestStack->getCurrentRequest();
      $outputPath = $request->query->get('outputPath');
      $mimeType = $request->query->get('mimeType');
      $type = $request->query->get('type');
      $execSystemId = $request->query->get('execSystemId');

      $attachmentFilename = basename($outputPath);

      $zip_param = FALSE;
      if ($type === "dir") {
        $mimeType = "application/zip";
        $attachmentFilename = "$attachmentFilename.zip";
        $zip_param = TRUE;
      }

      $jobUuid = $tapis_job->getTapisUUID();
      $jobOwnerId = $tapis_job->getOwnerId();

      // @todo Need to update this function to follow some other better approach
      // Current approach ends up storing file
      // in memory and then sending it to user.
      $body = $this->tapisJobProvider->getJobOutputFileDownload($tenantId, $jobUuid, $execSystemId,
        $outputPath, $zip_param, $jobOwnerId);
      $content = (string) $body;

      // Check if the request prefers a download.
      $preferDownload = boolval($request->query->get('download', 1));
      if ($preferDownload) {
        $response = new StreamedResponse(function () use ($content) {
          echo $content;
        });
        $response->headers->set('Content-Type', "application/octet-stream");
        $response->headers->set('Content-Disposition', 'attachment; filename="' . $attachmentFilename . '";');

        return $response;
      }
      else {
        // Load the supported image mimes from configuration.
        $config = $this->configFactory->get('tapis_job.config');
        $supported_image_mimes = explode(" ", $config->get('supported_image_mimes'));

        // Always serve image types as binary streams.
        if (in_array($mimeType, $supported_image_mimes)) {
          $response = new StreamedResponse(function () use ($content) {
            echo $content;
          });

          $response->headers->set('Content-Type', $mimeType);
          return $response;
        }
        else {
          // Format the file content using the text file viewer formatter.
          $formatted_output = $this->formatRawContent($content, $attachmentFilename, $mimeType);

          // Load the Prism CSS/JS URLs from configuration.
          $prism_css = $config->get('prism_css');
          $prism_js = $config->get('prism_js');

          // Prepare the assets array.
          $assets = [
            'css' => $prism_css,
            'js' => $prism_js,
          ];

          return new JsonResponse([
            'content' => $formatted_output,
            'assets' => $assets,
          ]);

        }
      }
    }
  }

  /**
   * Apply Prism.js syntax highlighting to the code.
   */
  public function formatRawContent($fileContent, $filename, $mimetype) {
    $config = $this->configFactory->get('tapis_job.config');
    $supported_extensions = explode(" ", $config->get('supported_extensions'));
    $supported_mimes = explode(" ", $config->get('supported_mimes'));

    $markup = '';
    // Get the file name extension and see if it is supported
    // by any of the known file formats.
    $ext = pathinfo($filename, PATHINFO_EXTENSION);

    if ($ext && (in_array($ext, $supported_extensions) || in_array($mimetype, $supported_mimes))) {
      $markup .= '<h3>File name: ' . $filename . '</h3>';
      $code = htmlentities($fileContent, ENT_QUOTES | ENT_HTML5, 'UTF-8');

      if (in_array($ext, $supported_extensions)) {
        $languageClass = $this->convertExtensionToPrismClassName($ext);
        $markup .= '<pre class="line-numbers"><code class="language-' . $languageClass . '">' . $code . '</code></pre>';

      }
      else {
        $markup .= '<pre class="line-numbers"><code class="language-' . $this->convertExtensionToPrismClassName("txt") . '">' . $code . '</code></pre>';
      }
    }
    else {
      $markup = '<h3>The file extension, "' . $ext . '" is not supported for viewing.</h3>';
    }

    return $markup;
  }

  /**
   * Map the extension to prime class name.
   */
  private function convertExtensionToPrismClassName($ext): string {
    $hTable = [
      "htm" => "markup",
      "html" => "markup",
      "xml" => "markup",
      "svg" => "markup",
      "md" => "markup",
      "mathml" => "markup",
      "css" => "css",
      "clike" => "clike",
      "pl" => "perl",
      "cpp" => "cpp",
      "hpp" => "cpp",
      "m" => "matlab",
      "mat" => "matlab",
      "sh" => "bash",
      "csh" => "bash",
      "ps1" => "powershell",
      "bat" => "powershell",
      "txt" => "textile",
      "csv" => "textile",
      "tsv" => "textile",
      "cs" => "csharp",
      "conf" => "apacheconf",
    ];

    return array_key_exists($ext, $hTable) ? $hTable[$ext] : $ext;
  }

  /**
   * Retrieves the directory contents for a specific tapis job.
   *
   * This method fetches a list of files and folders
   * within the specified directory in the execution system
   * associated with the tapis job. It formats the output
   * as a file tree for use in the front-end.
   *
   * @param \Drupal\tapis_job\TapisJobInterface|null $tapis_job
   *   The Tapis job entity.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   A JSON response containing the formatted file tree data.
   */
  public function getDirectoryContents(TapisJobInterface $tapis_job = NULL): JsonResponse {
    $request = $this->requestStack->getCurrentRequest();
    $recurse = "false";

    // Fetch tapis job details.
    $tenantId = $tapis_job->getTenantId();
    $jobOwnerId = $tapis_job->getOwnerId();
    $tapisJobDefinition = $this->tapisJobProvider->getJob($tenantId, $tapis_job->getTapisUUID(), $jobOwnerId);

    // Retrieve the execution system and directory information.
    $execSystemId = $tapisJobDefinition['execSystemId'];
    $execSystemExecDir = $tapisJobDefinition['execSystemExecDir'];
    $outputPath = $request->query->get('path', $execSystemExecDir);

    // Fetch the job outputs from the tapis API.
    $outputList = $this->tapisJobProvider->getJobOutputs($tenantId, $execSystemId, $outputPath, $recurse, $jobOwnerId);

    // Prepare the file tree data.
    $fileTreeData = [];
    if ($outputList['status_code'] === 200 && !empty($outputList['result'])) {
      foreach ($outputList['result'] as $outputItem) {
        // Determine the relative path.
        $path = substr($outputItem['path'], strpos('/' . $outputItem['path'], $execSystemExecDir) + strlen($execSystemExecDir));
        $id = $outputItem['path'];

        // Generate human-readable file size (if applicable).
        $fileSize = $outputItem['size'] ? " (" . $this->humanizeNumBytes($outputItem['size']) . ")" : '';

        // Generate URL for file or directory actions.
        $jobProxyURL = Url::fromRoute('entity.tapis_job.output_file', [
          'tapis_job' => $tapis_job->id(),
          'outputPath' => $outputItem['path'],
          'mimeType' => $outputItem['mimeType'],
          'type' => $outputItem['type'],
          'execSystemId' => $execSystemId,
        ]);

        // Add file or directory to the file tree data.
        $fileTreeData[] = [
          "id" => $id,
          'type' => $outputItem['type'] === "dir" ? 'folder' : 'file',
          "text" => basename($path) . "&nbsp;&nbsp;" . $fileSize,
          'icon' => 'fa fa-download',
          "children" => $outputItem['type'] === "dir",
          "a_attr" => [
            "href" => $jobProxyURL->toString(),
            'class' => $outputItem['type'] === "dir" ? 'dir-link' : 'file-link',
            'dataaction' => $outputItem['type'] === "dir" ? 'download-dir' : '',
            'filename' => basename($path),
          ],
        ];
      }
    }

    // Return the file tree data as a JSON response.
    return new JsonResponse($fileTreeData);
  }

  /**
   * {@inheritdoc}
   */
  private function humanizeNumBytes($bytes) {
    $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
    for ($i = 0; $bytes > 1024; $i++) {
      $bytes /= 1024;
    }
    return round($bytes, 2) . ' ' . $units[$i];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc