storage = Storage::disk($this::DISK); } public function storeFile(UploadedFile $file, Attachment $attachment): string|false { return $file->storeAs( $this->getPath($attachment), ['disk' => $this::DISK] ); } public function handleProjectChange(Task $parent): void { $newProjectId = $parent->getProjectId(); $parent->attachmentsRelation()->lazyById() ->each(fn(Attachment $attachment) => $this->moveAttachment($attachment, $newProjectId)); $parent->comments()->lazyById() ->each(fn(TaskComment $comment) => $comment->attachmentsRelation()->lazyById() ->each(fn(Attachment $attachment) => $this->moveAttachment($attachment, $newProjectId))); } public function moveAttachment(Attachment $attachment, $newProjectId): void { if ($attachment->status === AttachmentStatus::NOT_ATTACHED) { return; } $oldPath = $this->getPath($attachment); $oldProjectId = $attachment->project_id; $attachment->project_id = $newProjectId; $attachment->saveQuietly(); $newPath = $this->getPath($attachment); $fileExists = $this->storage->exists($oldPath); if (!$fileExists || !$this->storage->move($oldPath, $newPath)) { $attachment->project_id = $oldProjectId; $attachment->saveQuietly(); \Log::error("Unable to move attachment`s file", [ 'attachment_id' => $attachment->id, 'attachmentable_id' => $attachment->attachmentable_id, 'attachmentable_type' => $attachment->attachmentable_type, 'attachment_project_id' => $attachment->project_id, 'old_attachment_project_id' => $oldProjectId, 'old_path' => $oldPath, 'new_path' => $newPath ]); } } public function deleteAttachment(Attachment $attachment): void { if (($fileExists = $this->fileExists($attachment)) && $this->deleteFile($attachment)) { $attachment->deleteQuietly(); } elseif (!$fileExists) { $attachment->deleteQuietly(); } else { \Log::error("Unable to delete attachment`s file", [ 'attachment_id' => $attachment->id, 'attachmentable_id' => $attachment->attachmentable_id, 'attachmentable_type' => $attachment->attachmentable_type, 'attachment_project_id' => $attachment->project_id, 'path' => $this->getPath($attachment) ]); } } public function deleteAttachments(array $attachmentsIds): void { Attachment::whereIn('id', $attachmentsIds) ->each(fn (Attachment $attachment) => $this->deleteAttachment($attachment)); } public function handleParentDeletion(AttachmentAble $parent): void { $parent->attachmentsRelation()->lazyById() ->each(fn (Attachment $attachment) => $this->deleteAttachment($attachment)); } public function handleProjectDeletion(Project $project): void { Attachment::whereProjectId($project->id)->delete(); if ($this->storage->directoryExists($this->getProjectPath($project)) && !$this->storage->deleteDirectory($this->getProjectPath($project))) { \Log::error("Unable to delete project's attachments directory", [ 'project_id' => $project->id, 'path' => $this->getProjectPath($project) ]); } } public function fileExists(Attachment $attachment): bool { return $this->storage->exists($this->getPath($attachment)); } private function deleteFile(Attachment $attachment): bool { return $this->storage->delete($this->getPath($attachment)); } public function attach(AttachmentAble $parent, array $idsToAttach): void { $projectId = $parent->getProjectId(); Attachment::whereIn('id', $idsToAttach) ->each(function (Attachment $attachment) use ($parent, $projectId) { if ($attachment->status !== AttachmentStatus::NOT_ATTACHED) { return; } $tmpPath = $this->getPath($attachment); if ($this->storage->exists($tmpPath)) { $attachment->attachmentAbleRelation()->associate($parent); $attachment->status = AttachmentStatus::PROCESSING; $attachment->project_id = $projectId; $attachment->saveQuietly(); $newPath = $this->getPath($attachment); $this->storage->move($tmpPath, $newPath); VerifyAttachmentHash::dispatch($attachment)->afterCommit(); } }); } public function getProjectPath(Project $project): string { return "projects/{$project->id}"; } public function getPath(Attachment $attachment): string { return match ($attachment->status) { AttachmentStatus::NOT_ATTACHED => "users/{$attachment->user_id}/{$attachment->id}", AttachmentStatus::PROCESSING, AttachmentStatus::GOOD, AttachmentStatus::BAD => "projects/{$attachment->project_id}/{$attachment->id}", }; } public function getFullPath(Attachment $attachment): string { return $this->storage->path($this->getPath($attachment)); } public function readStream(Attachment $attachment) { return $this->storage->readStream($this->getPath($attachment)); } public function getHashAlgo(): string { return $this->storage->getConfig()['checksum_algo'] ?? 'sha512/256'; } public function getHashSum(Attachment|string $attachment): false|string { return $this->storage->checksum($this->callIfInstance($attachment, 'getPath'), ['checksum_algo' => $this->getHashAlgo()]); } public function getMimeType(Attachment|string $attachment): false|string { return $this->storage->mimeType($this->callIfInstance($attachment, 'getPath')); } private function callIfInstance(Attachment|string $attachment, $action) { return $attachment instanceof Attachment ? $this->{$action}($attachment) : $attachment; } public function verifyHash(Attachment $attachment): void { if ($attachment->status === AttachmentStatus::PROCESSING && $hash = $this->getHashSum($attachment)) { $attachment->hash = $hash; $attachment->status = AttachmentStatus::GOOD; } elseif ($attachment->status === AttachmentStatus::GOOD && $attachment->hash !== $this->getHashSum($attachment) ) { $attachment->status = AttachmentStatus::BAD; } $attachment->save(); } }