mirror of https://github.com/pixelfed/pixelfed
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
6.1 KiB
PHP
227 lines
6.1 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs\StoryPipeline;
|
|
|
|
use App\Services\StoryIndexService;
|
|
use App\Story;
|
|
use Exception;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Throwable;
|
|
|
|
class StoryRotateMedia implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
protected $story;
|
|
|
|
protected $newPath;
|
|
|
|
protected $oldPath;
|
|
|
|
/**
|
|
* The number of times the job may be attempted.
|
|
*/
|
|
public $tries = 3;
|
|
|
|
/**
|
|
* The maximum number of seconds the job can run.
|
|
*/
|
|
public $timeout = 300;
|
|
|
|
/**
|
|
* Calculate the number of seconds to wait before retrying the job.
|
|
*/
|
|
public function backoff(): array
|
|
{
|
|
return [30, 60, 120];
|
|
}
|
|
|
|
/**
|
|
* Create a new job instance.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct(Story $story)
|
|
{
|
|
$this->story = $story;
|
|
}
|
|
|
|
/**
|
|
* Execute the job.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function handle()
|
|
{
|
|
try {
|
|
$story = $this->story->fresh();
|
|
|
|
if (! $story) {
|
|
if (config('app.dev_log')) {
|
|
Log::warning('StoryRotateMedia: Story not found', ['story_id' => $this->story->id]);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ($story->local == false) {
|
|
return;
|
|
}
|
|
|
|
$this->oldPath = $story->path;
|
|
$this->newPath = $this->generateNewPath($this->oldPath);
|
|
|
|
if (! Storage::exists($this->oldPath)) {
|
|
if (config('app.dev_log')) {
|
|
Log::warning('StoryRotateMedia: Original file not found', [
|
|
'story_id' => $story->id,
|
|
'path' => $this->oldPath,
|
|
]);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
$this->rotateMedia($story);
|
|
|
|
if (config('app.dev_log')) {
|
|
Log::info('StoryRotateMedia: Successfully rotated media', [
|
|
'story_id' => $story->id,
|
|
'old_path' => $this->oldPath,
|
|
'new_path' => $this->newPath,
|
|
]);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
if (config('app.dev_log')) {
|
|
Log::error('StoryRotateMedia: Job failed', [
|
|
'story_id' => $this->story->id,
|
|
'error' => $e->getMessage(),
|
|
'trace' => $e->getTraceAsString(),
|
|
]);
|
|
}
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle the media rotation process
|
|
*/
|
|
protected function rotateMedia(Story $story)
|
|
{
|
|
DB::transaction(function () use ($story) {
|
|
if (! Storage::copy($this->oldPath, $this->newPath)) {
|
|
throw new Exception("Failed to copy file from {$this->oldPath} to {$this->newPath}");
|
|
}
|
|
|
|
if (! Storage::exists($this->newPath)) {
|
|
throw new Exception("New file was not created at {$this->newPath}");
|
|
}
|
|
|
|
$story->path = $this->newPath;
|
|
$story->bearcap_token = null;
|
|
|
|
if (! $story->save()) {
|
|
throw new Exception('Failed to update story record in database');
|
|
}
|
|
|
|
if (! Storage::delete($this->oldPath)) {
|
|
if (config('app.dev_log')) {
|
|
Log::warning('StoryRotateMedia: Failed to delete old file', [
|
|
'story_id' => $story->id,
|
|
'path' => $this->oldPath,
|
|
]);
|
|
}
|
|
}
|
|
});
|
|
|
|
$this->updateSearchIndex($story);
|
|
}
|
|
|
|
/**
|
|
* Update the search index
|
|
*/
|
|
protected function updateSearchIndex(Story $story)
|
|
{
|
|
try {
|
|
$index = app(StoryIndexService::class);
|
|
|
|
$index->removeStory($story->id, $story->profile_id);
|
|
|
|
usleep(random_int(100000, 500000));
|
|
|
|
$index->indexStory($story);
|
|
|
|
} catch (Exception $e) {
|
|
if (config('app.dev_log')) {
|
|
Log::error('StoryRotateMedia: Failed to update search index', [
|
|
'story_id' => $story->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate a new path for the rotated media
|
|
*/
|
|
protected function generateNewPath(string $oldPath): string
|
|
{
|
|
$paths = explode('/', $oldPath);
|
|
$name = array_pop($paths);
|
|
$ext = pathinfo($name, PATHINFO_EXTENSION);
|
|
$new = Str::random(13).'_'.Str::random(24).'_'.Str::random(3).'.'.$ext;
|
|
array_push($paths, $new);
|
|
|
|
return implode('/', $paths);
|
|
}
|
|
|
|
/**
|
|
* Handle a job failure.
|
|
*/
|
|
public function failed(Throwable $exception)
|
|
{
|
|
if (config('app.dev_log')) {
|
|
Log::error('StoryRotateMedia: Job permanently failed', [
|
|
'story_id' => $this->story->id,
|
|
'error' => $exception->getMessage(),
|
|
'attempts' => $this->attempts(),
|
|
]);
|
|
}
|
|
|
|
if ($this->newPath && Storage::exists($this->newPath)) {
|
|
try {
|
|
Storage::delete($this->newPath);
|
|
if (config('app.dev_log')) {
|
|
Log::info('StoryRotateMedia: Cleaned up orphaned file', [
|
|
'path' => $this->newPath,
|
|
]);
|
|
}
|
|
} catch (Exception $e) {
|
|
if (config('app.dev_log')) {
|
|
Log::error('StoryRotateMedia: Failed to cleanup orphaned file', [
|
|
'path' => $this->newPath,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine the time at which the job should timeout.
|
|
*/
|
|
public function retryUntil()
|
|
{
|
|
return now()->addHours(2);
|
|
}
|
|
}
|