<commit_before><?php

namespace App\Helpers;

use App\Helpers\FileStorage\IFileStorage;
use App\Helpers\FileStorage\IHashFileStorage;
use App\Helpers\FileStorage\IImmutableFile;
use App\Helpers\FileStorage\FileStorageException;
use App\Model\Entity\Submission;
use App\Model\Entity\Solution;
use App\Model\Entity\ReferenceExerciseSolution;
use App\Model\Entity\AssignmentSolution;
use App\Model\Entity\AssignmentSolutionSubmission;
use App\Model\Entity\ReferenceSolutionSubmission;
use App\Model\Entity\UploadedFile;
use App\Model\Entity\AttachmentFile;
use App\Helpers\TmpFilesHelper;
use App\Exceptions\InvalidArgumentException;
use Nette\Utils\Arrays;
use Nette\Http\FileUpload;
use Nette;
use DateTime;

/**
 * File storage manager provides access to underlying file storages.
 * Only this entity knows how the storage is configured and internally structured.
 * The rest of the core module should use only this manager when working with files
 * (with possible exception of working with temporary files which are just being uploaded/downloaded).
 */
class FileStorageManager
{
    use Nette\SmartObject;

    private const UPLOADS = 'uploads';
    private const ATTACHMENTS = 'attachments';
    private const SOLUTIONS = 'solutions';
    private const JOB_CONFIGS = 'job_configs';
    private const RESULTS = 'results';
    private const WORKER_DOWNLOADS = 'worker_downloads';
    private const WORKER_UPLOADS = 'worker_uploads';
    private const JOB_CONFIG_FILENAME = 'job-config.yml';
    private const JOB_RESULT_FILENAME = 'result/result.yml';

    /** @var IFileStorage */
    private $fileStorage;
    
    /** @var IHashFileStorage */
    private $hashStorage;

    /** @var TmpFilesHelper */
    private $tmpFilesHelper;

    /** @var string */
    private $apiUrl;

    /**
     * Initialize the manager by injecting dependencies and configuration
     * @param IFileStorage $fileStorage
     * @param IHashFileStorage $hashStorage
     * @param TmpFilesHelper $tmp
     * @param string $apiUrl injected configuration containing external URL to API (prefix for generated external URLs)
     */
    public function __construct(
        IFileStorage $fileStorage,
        IHashFileStorage $hashStorage,
        TmpFilesHelper $tmp,
        string $apiUrl
    ) {
        $this->fileStorage = $fileStorage;
        $this->hashStorage = $hashStorage;
        $this->tmpFilesHelper = $tmp;
        $this->apiUrl = $apiUrl;
    }

    private static function augmentDir(string $base, $entity): string
    {
        if ($entity->getSubdir()) {
            return $base . '/' . $entity->getSubdir();
        }
        return $base;
    }

    /**
     * Get path to temporary uploaded file.
     * @param UploadedFile $file uploaded file DB entity with file metadata
     * @return string path
     */
    private function getUploadedFilePath(UploadedFile $file): string
    {
        $dir = self::UPLOADS;
        $id = $file->getId();
        $name = $file->getName();
        return "$dir/${id}_$name";
    }

    /**
     * Store uploaded file data for associated UploadedFile db record.
     * @param UploadedFile $fileRecord database entity that corresponds to the uploaded file
     * @param FileUpload $fileData wrapper of the actual uploaded file to be saved
     * @throws InvalidArgumentException
     * @throws FileStorageException
     */
    public function storeUploadedFile(UploadedFile $fileRecord, FileUpload $fileData)
    {
        $path = $this->getUploadedFilePath($fileRecord);
        if (!$fileData->isOk()) {
            throw new InvalidArgumentException("fileData", "File was not uploaded successfully");
        }

        $this->fileStorage->storeFile($fileData->getTemporaryFile(), $path); // move, no overwrite
    }

    /**
     * Retrieve an plain uploaded file (in tmp storage).
     * @param UploadedFile $file corresponding database entity
     * @return IImmutableFile|null
     */
    public function getUploadedFile(UploadedFile $file): ?IImmutableFile
    {
        $path = $this->getUploadedFilePath($file);<commit_msg>Fixing problem with uploaded files rights.<commit_after><?php

namespace App\Helpers;

use App\Helpers\FileStorage\IFileStorage;
use App\Helpers\FileStorage\IHashFileStorage;
use App\Helpers\FileStorage\IImmutableFile;
use App\Helpers\FileStorage\FileStorageException;
use App\Model\Entity\Submission;
use App\Model\Entity\Solution;
use App\Model\Entity\ReferenceExerciseSolution;
use App\Model\Entity\AssignmentSolution;
use App\Model\Entity\AssignmentSolutionSubmission;
use App\Model\Entity\ReferenceSolutionSubmission;
use App\Model\Entity\UploadedFile;
use App\Model\Entity\AttachmentFile;
use App\Helpers\TmpFilesHelper;
use App\Exceptions\InvalidArgumentException;
use Nette\Utils\Arrays;
use Nette\Http\FileUpload;
use Nette;
use DateTime;

/**
 * File storage manager provides access to underlying file storages.
 * Only this entity knows how the storage is configured and internally structured.
 * The rest of the core module should use only this manager when working with files
 * (with possible exception of working with temporary files which are just being uploaded/downloaded).
 */
class FileStorageManager
{
    use Nette\SmartObject;

    private const UPLOADS = 'uploads';
    private const ATTACHMENTS = 'attachments';
    private const SOLUTIONS = 'solutions';
    private const JOB_CONFIGS = 'job_configs';
    private const RESULTS = 'results';
    private const WORKER_DOWNLOADS = 'worker_downloads';
    private const WORKER_UPLOADS = 'worker_uploads';
    private const JOB_CONFIG_FILENAME = 'job-config.yml';
    private const JOB_RESULT_FILENAME = 'result/result.yml';

    /** @var IFileStorage */
    private $fileStorage;
    
    /** @var IHashFileStorage */
    private $hashStorage;

    /** @var TmpFilesHelper */
    private $tmpFilesHelper;

    /** @var string */
    private $apiUrl;

    /**
     * Initialize the manager by injecting dependencies and configuration
     * @param IFileStorage $fileStorage
     * @param IHashFileStorage $hashStorage
     * @param TmpFilesHelper $tmp
     * @param string $apiUrl injected configuration containing external URL to API (prefix for generated external URLs)
     */
    public function __construct(
        IFileStorage $fileStorage,
        IHashFileStorage $hashStorage,
        TmpFilesHelper $tmp,
        string $apiUrl
    ) {
        $this->fileStorage = $fileStorage;
        $this->hashStorage = $hashStorage;
        $this->tmpFilesHelper = $tmp;
        $this->apiUrl = $apiUrl;
    }

    private static function augmentDir(string $base, $entity): string
    {
        if ($entity->getSubdir()) {
            return $base . '/' . $entity->getSubdir();
        }
        return $base;
    }

    /**
     * Get path to temporary uploaded file.
     * @param UploadedFile $file uploaded file DB entity with file metadata
     * @return string path
     */
    private function getUploadedFilePath(UploadedFile $file): string
    {
        $dir = self::UPLOADS;
        $id = $file->getId();
        $name = $file->getName();
        return "$dir/${id}_$name";
    }

    /**
     * Store uploaded file data for associated UploadedFile db record.
     * @param UploadedFile $fileRecord database entity that corresponds to the uploaded file
     * @param FileUpload $fileData wrapper of the actual uploaded file to be saved
     * @throws InvalidArgumentException
     * @throws FileStorageException
     */
    public function storeUploadedFile(UploadedFile $fileRecord, FileUpload $fileData)
    {
        $path = $this->getUploadedFilePath($fileRecord);
        if (!$fileData->isOk()) {
            throw new InvalidArgumentException("fileData", "File was not uploaded successfully");
        }

        $this->fileStorage->storeFile($fileData->getTemporaryFile(), $path, false); // copy (moving may not be safe), no overwrite
    }

    /**
     * Retrieve an plain uploaded file (in tmp storage).
     * @param UploadedFile $file corresponding database entity
     * @return IImmutableFile|null
     */
    public function getUploadedFile(UploadedFile $file): ?IImmutableFile
    {
        $path = $this->getUploadedFilePath($file);
{'fraction_non_alphanumeric': 0.09610928161970972, 'fraction_numerical': 0.0, 'mean_word_length': 3.318062137967351, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2117257', 'n_tokens_mistral': 2293, 'n_tokens_neox': 2234, 'n_words': 656}