PHP Classes

File: scripts/process_packets.php

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   scripts/process_packets.php   Download  
File: scripts/process_packets.php
Role: Example script
Content type: text/plain
Description: Example script
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 6,546 bytes
 

Contents

Class file image Download
#!/usr/bin/env php
<?php

chdir
(__DIR__);

// Packet processing script - run this via cron or as a service

require_once __DIR__ . '/../vendor/autoload.php';
require_once
__DIR__ . '/../src/functions.php';

use
BinktermPHP\BinkdProcessor;
use
BinktermPHP\Binkp\Logger;
use
BinktermPHP\Config;
use
BinktermPHP\Database;
use
BinktermPHP\TicFileProcessor;

// Initialize database
Database::getInstance();

$logger = new Logger(
   
Config::getLogPath('packets.log'),
   
Config::env('PROCESS_PACKETS_LOG_LEVEL', 'INFO'),
   
true
);

// Ensure only one instance runs at a time
$lockFile = __DIR__ . '/../data/run/process_packets.lock';
if (!
is_dir(dirname($lockFile))) {
   
mkdir(dirname($lockFile), 0755, true);
}
$lockFh = fopen($lockFile, 'c');
if (!
$lockFh || !flock($lockFh, LOCK_EX | LOCK_NB)) {
   
$logger->warning('Another instance of process_packets.php is already running. Exiting.');
    exit(
0);
}

/**
 * Process TIC files from inbound directory.
 */
function processInboundTicFiles(TicFileProcessor $ticProcessor, Logger $logger): int
{
   
$inboundPath = __DIR__ . '/../data/inbound';
   
$processedCount = 0;

   
// Find all .tic files (both lowercase and uppercase)
   
$ticFiles = array_merge(
       
glob($inboundPath . '/*.tic') ?: [],
       
glob($inboundPath . '/*.TIC') ?: []
    );
    if (!
$ticFiles || count($ticFiles) === 0) {
        return
0;
    }

    foreach (
$ticFiles as $ticPath) {
       
$ticFilename = basename($ticPath);

       
// Determine data filename (TIC usually references filename without .tic)
        // Read first few lines to get the File field
       
$ticContent = file_get_contents($ticPath);
        if (
preg_match('/^File\s+(.+)$/im', $ticContent, $matches)) {
           
$dataFilename = trim($matches[1]);
           
$dataPath = $inboundPath . '/' . $dataFilename;

            if (
file_exists($dataPath)) {
               
// Both TIC and data file exist - process them
               
$logger->info("Processing TIC: $ticFilename -> $dataFilename");

               
$result = $ticProcessor->processTicFile($ticPath, $dataPath);

                if (
$result['success']) {
                   
$logger->info(" OK Stored in area: {$result['area']} (file_id={$result['file_id']})");

                    if (isset(
$result['duplicate']) && $result['duplicate']) {
                       
$logger->warning(" WARN Duplicate file (skipped)");
                    }

                   
// Clean up TIC file (data file was cleaned up by processor)
                   
unlink($ticPath);
                   
$processedCount++;
                } else {
                   
$errMsg = $result['error'] ?? 'unknown error';
                   
$errCode = $result['error_code'] ?? '';
                   
$logger->error("[TIC] FAILED {$ticFilename}/{$dataFilename}: [{$errCode}] {$errMsg}");

                   
// Move failed TIC to .failed directory for manual review
                   
$failedDir = $inboundPath . '/.failed';
                    if (!
is_dir($failedDir)) {
                       
mkdir($failedDir, 0755, true);
                    }
                   
rename($ticPath, $failedDir . '/' . $ticFilename);
                    if (
file_exists($dataPath)) {
                       
rename($dataPath, $failedDir . '/' . $dataFilename);
                    }
                }
            } else {
               
// TIC exists but data file missing - will be retried next run
               
$logger->warning("TIC file without data file: $ticFilename (waiting for $dataFilename)");
            }
        } else {
           
$logger->error("Invalid TIC file (no File field): $ticFilename");
           
unlink($ticPath);
        }
    }

    return
$processedCount;
}

// Create processors
$processor = new BinkdProcessor();
$ticProcessor = new TicFileProcessor();

try {
   
$logger->info('Starting packet processing...');

   
// Process inbound packets
   
$processed = $processor->processInboundPackets();
   
$logger->info("Processed {$processed} inbound packets");

   
// Process TIC files (if feature is enabled)
   
if (\BinktermPHP\FileAreaManager::isFeatureEnabled()) {
       
$ticProcessed = processInboundTicFiles($ticProcessor, $logger);
        if (
$ticProcessed > 0) {
           
$logger->info("Processed {$ticProcessed} TIC files");
        }
    }

   
// Clean up old packet records (older than 6 months)
   
$cleaned = $processor->cleanupOldPackets();
    if (
$cleaned) {
       
$logger->info("Cleaned up {$cleaned} old packet records");
    }

   
// Delete stale unprocessed files only when explicitly enabled in .env.
    // Default behavior is to quarantine them in data/inbound/unprocessed/.
   
$deleteUnprocessedFiles = filter_var(
        (string)
Config::env('BINKP_DELETE_UNPROCESSED_FILES', 'false'),
       
FILTER_VALIDATE_BOOLEAN
   
);

   
// Handle stale unrecognized/unprocessed files in inbound.
    // TIC files are left in place because they may still be waiting for their data file.
   
$inboundPath = __DIR__ . '/../data/inbound';
   
$unprocessedDir = $inboundPath . '/unprocessed';
   
$leftover = array_filter(glob($inboundPath . '/*') ?: [], 'is_file');
   
$staleCutoff = time() - 86400;

    if (!
$deleteUnprocessedFiles && !is_dir($unprocessedDir)) {
       
mkdir($unprocessedDir, 0755, true);
    }

    foreach (
$leftover as $file) {
       
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        if (
$ext === 'tic') {
            continue;
// Leave TIC files ? they may still be waiting for their data file
       
}
        if (
$ext === 'tmp') {
            continue;
// Leave .tmp files ? they are being actively received by the binkp session
       
}

       
$mtime = @filemtime($file);
        if (
$mtime === false || $mtime > $staleCutoff) {
            continue;
        }

        if (!
$deleteUnprocessedFiles) {
           
$dest = $unprocessedDir . '/' . basename($file);
            if (
rename($file, $dest)) {
               
$logger->info('Moved stale unprocessed file to unprocessed/: ' . basename($file));
            }
            continue;
        }

        if (
unlink($file)) {
           
$logger->info('Deleted stale unprocessed file: ' . basename($file));
        }
    }

   
// TODO: Process outbound queue (send pending messages)

   
$logger->info('Packet processing completed');
} catch (
Exception $e) {
   
$logger->error('Error: ' . $e->getMessage());
   
flock($lockFh, LOCK_UN);
   
fclose($lockFh);
    exit(
1);
}

flock($lockFh, LOCK_UN);
fclose($lockFh);