Passed
Push — master ( 96ef05...f81447 )
by Darko
09:59
created

TmuxTaskRunner::buildSleepCommand()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 10
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
namespace App\Services\Tmux;
4
5
use App\Models\Settings;
6
use Blacklight\ColorCLI;
7
8
/**
9
 * Service for running tasks in tmux panes
10
 */
11
class TmuxTaskRunner
12
{
13
    protected TmuxPaneManager $paneManager;
14
15
    protected ColorCLI $colorCli;
16
17
    protected string $sessionName;
18
19
    public function __construct(string $sessionName)
20
    {
21
        $this->sessionName = $sessionName;
22
        $this->paneManager = new TmuxPaneManager($sessionName);
23
        $this->colorCli = new ColorCLI;
24
    }
25
26
    /**
27
     * Run a task in a specific pane
28
     */
29
    public function runTask(string $taskName, array $config): bool
30
    {
31
        $pane = $config['pane'] ?? null;
32
        $command = $config['command'] ?? null;
33
        $enabled = $config['enabled'] ?? true;
34
        $workAvailable = $config['work_available'] ?? true;
35
36
        if (! $pane || ! $command) {
37
            return false;
38
        }
39
40
        // Check if task is enabled and has work
41
        if (! $enabled) {
42
            return $this->disablePane($pane, $taskName, 'disabled in settings');
43
        }
44
45
        if (! $workAvailable) {
46
            return $this->disablePane($pane, $taskName, 'no work available');
47
        }
48
49
        // Respawn the pane with the command
50
        return $this->paneManager->respawnPane($pane, $command);
51
    }
52
53
    /**
54
     * Disable a pane with a message
55
     */
56
    protected function disablePane(string $pane, string $taskName, string $reason): bool
57
    {
58
        $color = $this->getRandomColor();
59
        $message = "echo \"\\033[38;5;{$color}m\\n{$taskName} has been disabled: {$reason}\"";
60
61
        return $this->paneManager->respawnPane($pane, $message, kill: true);
62
    }
63
64
    /**
65
     * Build a command with logging
66
     */
67
    public function buildCommand(string $baseCommand, array $options = []): string
68
    {
69
        $parts = [$baseCommand];
70
71
        // Add sleep timer at the end if specified
72
        if (isset($options['sleep'])) {
73
            $sleepCommand = $this->buildSleepCommand($options['sleep']);
74
            $parts[] = "date +'%Y-%m-%d %T'";
75
            $parts[] = $sleepCommand;
76
        }
77
78
        // Add logging if enabled
79
        if (isset($options['log_pane'])) {
80
            $logFile = $this->getLogFile($options['log_pane']);
81
            $command = implode('; ', $parts);
82
83
            return "{$command} 2>&1 | tee -a {$logFile}";
84
        }
85
86
        return implode('; ', $parts);
87
    }
88
89
    /**
90
     * Build sleep command
91
     */
92
    protected function buildSleepCommand(int $seconds): string
93
    {
94
        $niceness = Settings::settingValue('niceness') ?? 2;
95
        $sleepScript = base_path('app/Services/Tmux/Scripts/showsleep.php');
96
97
        if (file_exists($sleepScript)) {
98
            return "nice -n{$niceness} php {$sleepScript} {$seconds}";
99
        }
100
101
        return "sleep {$seconds}";
102
    }
103
104
    /**
105
     * Get log file path for a pane
106
     */
107
    protected function getLogFile(string $paneName): string
108
    {
109
        $logsEnabled = (int) Settings::settingValue('write_logs') === 1;
110
111
        if (! $logsEnabled) {
112
            return '/dev/null';
113
        }
114
115
        $logDir = config('tmux.paths.logs', storage_path('logs/tmux'));
116
117
        if (! is_dir($logDir)) {
118
            mkdir($logDir, 0755, true);
119
        }
120
121
        $date = now()->format('Y_m_d');
122
123
        return "{$logDir}/{$paneName}-{$date}.log";
124
    }
125
126
    /**
127
     * Get a random color for terminal output
128
     */
129
    protected function getRandomColor(): int
130
    {
131
        $start = (int) Settings::settingValue('colors_start') ?? 0;
132
        $end = (int) Settings::settingValue('colors_end') ?? 255;
133
        $exclude = Settings::settingValue('colors_exc') ?? '';
134
135
        if (empty($exclude)) {
136
            return random_int($start, $end);
137
        }
138
139
        $exceptions = array_map('intval', explode(',', $exclude));
140
        sort($exceptions);
141
142
        $number = random_int($start, $end - count($exceptions));
143
144
        foreach ($exceptions as $exception) {
145
            if ($number >= $exception) {
146
                $number++;
147
            } else {
148
                break;
149
            }
150
        }
151
152
        return $number;
153
    }
154
155
    /**
156
     * Run the IRC scraper
157
     */
158
    public function runIRCScraper(array $config): bool
159
    {
160
        $runScraper = (int) ($config['constants']['run_ircscraper'] ?? 0);
161
        $pane = '3.0';
162
163
        if ($runScraper !== 1) {
164
            return $this->disablePane($pane, 'IRC Scraper', 'disabled in settings');
165
        }
166
167
        $scraperScript = base_path('misc/IRCScraper/scrape.php');
168
169
        if (! file_exists($scraperScript)) {
170
            return $this->disablePane($pane, 'IRC Scraper', 'script not found');
171
        }
172
173
        $niceness = Settings::settingValue('niceness') ?? 2;
174
        $command = "nice -n{$niceness} php {$scraperScript}";
175
        $command = $this->buildCommand($command, ['log_pane' => 'scraper']);
176
177
        return $this->paneManager->respawnPane($pane, $command);
178
    }
179
180
    /**
181
     * Run binaries update
182
     */
183
    public function runBinariesUpdate(array $config): bool
184
    {
185
        $enabled = (int) ($config['settings']['binaries_run'] ?? 0);
186
        $killswitch = $config['killswitch']['pp'] ?? false;
187
        $pane = '0.1';
188
189
        if (! $enabled) {
190
            return $this->disablePane($pane, 'Update Binaries', 'disabled in settings');
191
        }
192
193
        if ($killswitch) {
194
            return $this->disablePane($pane, 'Update Binaries', 'postprocess kill limit exceeded');
195
        }
196
197
        $artisanCommand = match ((int) $enabled) {
198
            1 => 'multiprocessing:safe binaries',
199
            default => null,
200
        };
201
202
        if (! $artisanCommand) {
203
            return false;
204
        }
205
206
        $niceness = Settings::settingValue('niceness') ?? 2;
207
        $command = "nice -n{$niceness} ".PHP_BINARY." artisan {$artisanCommand}";
208
        $sleep = (int) ($config['settings']['bins_timer'] ?? 60);
209
        $command = $this->buildCommand($command, ['log_pane' => 'binaries', 'sleep' => $sleep]);
210
211
        return $this->paneManager->respawnPane($pane, $command);
212
    }
213
214
    /**
215
     * Run backfill
216
     */
217
    public function runBackfill(array $config): bool
218
    {
219
        $enabled = (int) ($config['settings']['backfill'] ?? 0);
220
        $collKillswitch = $config['killswitch']['coll'] ?? false;
221
        $ppKillswitch = $config['killswitch']['pp'] ?? false;
222
        $pane = '0.2';
223
224
        if (! $enabled) {
225
            return $this->disablePane($pane, 'Backfill', 'disabled in settings');
226
        }
227
228
        if ($collKillswitch || $ppKillswitch) {
229
            return $this->disablePane($pane, 'Backfill', 'kill limit exceeded');
230
        }
231
232
        $artisanCommand = match ((int) $enabled) {
233
            1 => 'multiprocessing:backfill',
234
            4 => 'multiprocessing:safe backfill',
235
            default => null,
236
        };
237
238
        if (! $artisanCommand) {
239
            return false;
240
        }
241
242
        // Calculate sleep time (progressive if enabled)
243
        $baseSleep = (int) ($config['settings']['back_timer'] ?? 600);
244
        $collections = (int) ($config['counts']['now']['collections_table'] ?? 0);
245
        $progressive = (int) ($config['settings']['progressive'] ?? 0);
246
247
        $sleep = ($progressive === 1 && floor($collections / 500) > $baseSleep)
248
            ? floor($collections / 500)
249
            : $baseSleep;
250
251
        $niceness = Settings::settingValue('niceness') ?? 2;
252
        $command = "nice -n{$niceness} ".PHP_BINARY." artisan {$artisanCommand}";
253
        $command = $this->buildCommand($command, ['log_pane' => 'backfill', 'sleep' => $sleep]);
254
255
        return $this->paneManager->respawnPane($pane, $command);
256
    }
257
258
    /**
259
     * Run releases update
260
     */
261
    public function runReleasesUpdate(array $config): bool
262
    {
263
        $enabled = (int) ($config['settings']['releases_run'] ?? 0);
264
        $pane = $config['pane'] ?? '0.3';
265
266
        if (! $enabled) {
267
            return $this->disablePane($pane, 'Update Releases', 'disabled in settings');
268
        }
269
270
        $niceness = Settings::settingValue('niceness') ?? 2;
271
        $command = "nice -n{$niceness} ".PHP_BINARY.' artisan multiprocessing:releases';
272
        $sleep = (int) ($config['settings']['rel_timer'] ?? 60);
273
        $command = $this->buildCommand($command, ['log_pane' => 'releases', 'sleep' => $sleep]);
274
275
        return $this->paneManager->respawnPane($pane, $command);
276
    }
277
278
    /**
279
     * Run a specific pane task based on task name
280
     *
281
     * @param  string  $taskName  The name of the task to run
282
     * @param  array  $config  Configuration for the task (target pane, etc.)
283
     * @param  array  $runVar  Runtime variables and settings
284
     * @return bool Success status
285
     */
286
    public function runPaneTask(string $taskName, array $config, array $runVar): bool
0 ignored issues
show
Unused Code introduced by
The parameter $config is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

286
    public function runPaneTask(string $taskName, /** @scrutinizer ignore-unused */ array $config, array $runVar): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
287
    {
288
        $sequential = (int) ($runVar['constants']['sequential'] ?? 0);
289
290
        return match ($taskName) {
291
            'main' => $this->runMainTask($sequential, $runVar),
292
            'fixnames' => $this->runFixNamesTask($runVar),
293
            'removecrap' => $this->runRemoveCrapTask($runVar),
294
            'ppadditional' => $this->runPostProcessAdditional($runVar),
295
            'nonamazon' => $this->runNonAmazonTask($runVar),
296
            'amazon' => $this->runAmazonTask($runVar),
297
            'scraper' => $this->runIRCScraper($runVar),
298
            default => false,
299
        };
300
    }
301
302
    /**
303
     * Run main task (varies by sequential mode)
304
     */
305
    protected function runMainTask(int $sequential, array $runVar): bool
306
    {
307
        return match ($sequential) {
308
            0 => $this->runMainNonSequential($runVar),
309
            1 => $this->runMainBasic($runVar),
310
            2 => $this->runMainSequential($runVar),
311
            default => false,
312
        };
313
    }
314
315
    /**
316
     * Run main non-sequential task (binaries, backfill, releases)
317
     */
318
    protected function runMainNonSequential(array $runVar): bool
319
    {
320
        // This runs in pane 0.1, 0.2, 0.3
321
        // For now, delegate to existing methods
322
        $this->runBinariesUpdate($runVar);
323
        $this->runBackfill($runVar);
324
        $this->runReleasesUpdate(array_merge($runVar, ['pane' => '0.3']));
325
326
        return true;
327
    }
328
329
    /**
330
     * Run main basic sequential task (just releases)
331
     */
332
    protected function runMainBasic(array $runVar): bool
333
    {
334
        return $this->runReleasesUpdate(array_merge($runVar, ['pane' => '0.1']));
335
    }
336
337
    /**
338
     * Run main full sequential task
339
     */
340
    protected function runMainSequential(array $runVar): bool
0 ignored issues
show
Unused Code introduced by
The parameter $runVar is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

340
    protected function runMainSequential(/** @scrutinizer ignore-unused */ array $runVar): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
341
    {
342
        // Full sequential mode - runs sequential script
343
        $pane = '0.1';
344
        $script = base_path('misc/update/nix/tmux/bin/sequential.php');
345
346
        if (! file_exists($script)) {
347
            return $this->disablePane($pane, 'Sequential', 'script not found');
348
        }
349
350
        $niceness = Settings::settingValue('niceness') ?? 2;
351
        $command = "nice -n{$niceness} php {$script}";
352
        $command = $this->buildCommand($command, ['log_pane' => 'sequential']);
353
354
        return $this->paneManager->respawnPane($pane, $command);
355
    }
356
357
    /**
358
     * Run fix release names task
359
     */
360
    protected function runFixNamesTask(array $runVar): bool
361
    {
362
        $enabled = (int) ($runVar['settings']['fix_names'] ?? 0);
363
        $work = (int) ($runVar['counts']['now']['processrenames'] ?? 0);
364
        $pane = '1.0';
365
366
        if ($enabled !== 1) {
367
            return $this->disablePane($pane, 'Fix Release Names', 'disabled in settings');
368
        }
369
370
        if ($work === 0) {
371
            return $this->disablePane($pane, 'Fix Release Names', 'no releases to process');
372
        }
373
374
        $artisan = base_path('artisan');
375
        $log = $this->getLogFile('fixnames');
376
377
        // Run multiple fix-names passes
378
        $commands = [];
379
        foreach ([3, 5, 7, 9, 11, 13, 15, 17, 19] as $level) {
380
            $commands[] = "php {$artisan} releases:fix-names {$level} --update --category=other --set-status --show 2>&1 | tee -a {$log}";
381
        }
382
383
        $sleep = (int) ($runVar['settings']['fix_timer'] ?? 300);
384
        $allCommands = implode('; ', $commands);
385
        $fullCommand = "{$allCommands}; date +'%Y-%m-%d %T'; sleep {$sleep}";
386
387
        return $this->paneManager->respawnPane($pane, $fullCommand);
388
    }
389
390
    /**
391
     * Run remove crap releases task
392
     */
393
    protected function runRemoveCrapTask(array $runVar): bool
394
    {
395
        $enabled = (int) ($runVar['settings']['fix_crap'] ?? 0);
396
        $pane = '1.1';
397
398
        if ($enabled !== 1) {
399
            return $this->disablePane($pane, 'Remove Crap', 'disabled in settings');
400
        }
401
402
        $option = (int) ($runVar['settings']['fix_crap_opt'] ?? 1);
403
        $script = base_path('misc/update/nix/tmux/bin/removecrap.php');
404
405
        if (! file_exists($script)) {
406
            return $this->disablePane($pane, 'Remove Crap', 'script not found');
407
        }
408
409
        $niceness = Settings::settingValue('niceness') ?? 2;
410
        $command = "nice -n{$niceness} php {$script} {$option}";
411
        $sleep = (int) ($runVar['settings']['crap_timer'] ?? 300);
412
        $command = $this->buildCommand($command, ['log_pane' => 'removecrap', 'sleep' => $sleep]);
413
414
        return $this->paneManager->respawnPane($pane, $command);
415
    }
416
417
    /**
418
     * Run post-process additional task
419
     */
420
    protected function runPostProcessAdditional(array $runVar): bool
421
    {
422
        $enabled = (int) ($runVar['settings']['post'] ?? 0);
423
        $pane = '2.0';
424
425
        if ($enabled !== 1) {
426
            return $this->disablePane($pane, 'Post-process Additional', 'disabled in settings');
427
        }
428
429
        $niceness = Settings::settingValue('niceness') ?? 2;
430
        $command = "nice -n{$niceness} ".PHP_BINARY.' artisan update:postprocess additional true';
431
        $sleep = (int) ($runVar['settings']['post_timer'] ?? 300);
432
        $command = $this->buildCommand($command, ['log_pane' => 'post_additional', 'sleep' => $sleep]);
433
434
        return $this->paneManager->respawnPane($pane, $command);
435
    }
436
437
    /**
438
     * Run non-Amazon post-processing (TV, Movies, Anime)
439
     */
440
    protected function runNonAmazonTask(array $runVar): bool
441
    {
442
        $enabled = (int) ($runVar['settings']['post_non'] ?? 0);
443
        $pane = '2.1';
444
445
        if ($enabled !== 1) {
446
            return $this->disablePane($pane, 'Post-process Non-Amazon', 'disabled in settings');
447
        }
448
449
        $hasWork = (int) ($runVar['counts']['now']['processmovies'] ?? 0) > 0
450
            || (int) ($runVar['counts']['now']['processtv'] ?? 0) > 0
451
            || (int) ($runVar['counts']['now']['processanime'] ?? 0) > 0;
452
453
        if (! $hasWork) {
454
            return $this->disablePane($pane, 'Post-process Non-Amazon', 'no movies/tv/anime to process');
455
        }
456
457
        $niceness = Settings::settingValue('niceness') ?? 2;
458
        $log = $this->getLogFile('post_non');
459
460
        $artisan = PHP_BINARY.' artisan';
461
        $commands = [
462
            "{$artisan} update:postprocess tv true 2>&1 | tee -a {$log}",
463
            "{$artisan} update:postprocess movies true 2>&1 | tee -a {$log}",
464
            "{$artisan} update:postprocess anime true 2>&1 | tee -a {$log}",
465
        ];
466
467
        $sleep = (int) ($runVar['settings']['post_timer_non'] ?? 300);
468
        $allCommands = "nice -n{$niceness} ".implode('; nice -n{$niceness} ', $commands);
469
        $fullCommand = "{$allCommands}; date +'%Y-%m-%d %T'; sleep {$sleep}";
470
471
        return $this->paneManager->respawnPane($pane, $fullCommand);
472
    }
473
474
    /**
475
     * Run Amazon post-processing (Books, Music, Games, Console, XXX)
476
     */
477
    protected function runAmazonTask(array $runVar): bool
478
    {
479
        $enabled = (int) ($runVar['settings']['post_amazon'] ?? 0);
480
        $pane = '2.2';
481
482
        if ($enabled !== 1) {
483
            return $this->disablePane($pane, 'Post-process Amazon', 'disabled in settings');
484
        }
485
486
        $hasWork = (int) ($runVar['counts']['now']['processmusic'] ?? 0) > 0
487
            || (int) ($runVar['counts']['now']['processbooks'] ?? 0) > 0
488
            || (int) ($runVar['counts']['now']['processconsole'] ?? 0) > 0
489
            || (int) ($runVar['counts']['now']['processgames'] ?? 0) > 0
490
            || (int) ($runVar['counts']['now']['processxxx'] ?? 0) > 0;
491
492
        if (! $hasWork) {
493
            return $this->disablePane($pane, 'Post-process Amazon', 'no music/books/games to process');
494
        }
495
496
        $niceness = Settings::settingValue('niceness') ?? 2;
497
        $command = "nice -n{$niceness} ".PHP_BINARY.' artisan update:postprocess amazon true';
498
        $sleep = (int) ($runVar['settings']['post_timer_amazon'] ?? 300);
499
        $command = $this->buildCommand($command, ['log_pane' => 'post_amazon', 'sleep' => $sleep]);
500
501
        return $this->paneManager->respawnPane($pane, $command);
502
    }
503
}
504