Add verbose mode, progress indicators, and missing media log
- Add --verbose flag to sync commands for detailed API request/response output - Add progress indicators (.=#xPpE|) for compact sync output - Implement exponential backoff (1s, 2s, 4s, 8s, 16s) for media downloads - Log failed media downloads to wp-content/uploads/mls-missing-media.log - Add 'wp mls cache missing' command to view/clear the log - Retry on rate limit (429) and server errors (5xx) - Update documentation with new features Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -228,11 +228,18 @@ class MLS_CLI {
|
||||
* [--force]
|
||||
* : Force re-download of media (for media command)
|
||||
*
|
||||
* [--quiet]
|
||||
* : Suppress progress output
|
||||
*
|
||||
* [--verbose]
|
||||
* : Show detailed output including API requests and responses
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp mls sync full
|
||||
* wp mls sync full --dry-run --limit=10
|
||||
* wp mls sync incremental
|
||||
* wp mls sync incremental --verbose
|
||||
* wp mls sync media --limit=100
|
||||
* wp mls sync resume --id=5
|
||||
*
|
||||
@@ -242,16 +249,58 @@ class MLS_CLI {
|
||||
$type = isset($args[0]) ? $args[0] : 'incremental';
|
||||
$dry_run = isset($assoc_args['dry-run']);
|
||||
$limit = isset($assoc_args['limit']) ? (int) $assoc_args['limit'] : null;
|
||||
$quiet = isset($assoc_args['quiet']);
|
||||
$verbose = isset($assoc_args['verbose']);
|
||||
|
||||
$sync_engine = $this->plugin->get_sync_engine();
|
||||
|
||||
// Progress callback for CLI
|
||||
$progress = null;
|
||||
$progress_callback = function($stats) use (&$progress) {
|
||||
if ($progress) {
|
||||
$progress->tick();
|
||||
}
|
||||
};
|
||||
$progress_callback = null;
|
||||
if (!$quiet) {
|
||||
$progress_callback = function($event, $data = array()) use ($verbose) {
|
||||
if ($verbose) {
|
||||
// Verbose mode: full line output
|
||||
$this->output_verbose_event($event, $data);
|
||||
} else {
|
||||
// Compact mode: single character symbols
|
||||
switch ($event) {
|
||||
case 'property_created':
|
||||
echo '.';
|
||||
break;
|
||||
case 'property_updated':
|
||||
echo '#';
|
||||
break;
|
||||
case 'property_deleted':
|
||||
echo 'x';
|
||||
break;
|
||||
case 'property_skipped':
|
||||
echo '-';
|
||||
break;
|
||||
case 'property_error':
|
||||
echo '!';
|
||||
break;
|
||||
case 'media_downloaded':
|
||||
echo 'P';
|
||||
break;
|
||||
case 'media_skipped':
|
||||
echo 'p';
|
||||
break;
|
||||
case 'media_error':
|
||||
echo 'E';
|
||||
break;
|
||||
case 'page_complete':
|
||||
echo '|';
|
||||
break;
|
||||
case 'api_request':
|
||||
// Silent in compact mode
|
||||
break;
|
||||
case 'api_response':
|
||||
// Silent in compact mode
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'full':
|
||||
@@ -259,8 +308,14 @@ class MLS_CLI {
|
||||
if ($dry_run) {
|
||||
WP_CLI::line('DRY RUN - No changes will be made');
|
||||
}
|
||||
if (!$quiet) {
|
||||
$this->print_progress_legend($verbose);
|
||||
}
|
||||
|
||||
$result = $sync_engine->run_full_sync($dry_run, $limit, $progress_callback);
|
||||
if (!$quiet) {
|
||||
echo "\n";
|
||||
}
|
||||
$this->output_sync_result($result);
|
||||
break;
|
||||
|
||||
@@ -269,17 +324,30 @@ class MLS_CLI {
|
||||
if ($dry_run) {
|
||||
WP_CLI::line('DRY RUN - No changes will be made');
|
||||
}
|
||||
if (!$quiet) {
|
||||
$this->print_progress_legend($verbose);
|
||||
}
|
||||
|
||||
$result = $sync_engine->run_incremental_sync($dry_run, $progress_callback);
|
||||
if (!$quiet) {
|
||||
echo "\n";
|
||||
}
|
||||
$this->output_sync_result($result);
|
||||
break;
|
||||
|
||||
case 'media':
|
||||
WP_CLI::line('Downloading pending media...');
|
||||
if (!$quiet) {
|
||||
WP_CLI::line('Legend: P=downloaded p=skipped E=error');
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
$media_handler = $this->plugin->get_media_handler();
|
||||
$result = $media_handler->download_pending($limit ?: 100);
|
||||
$result = $media_handler->download_pending($limit ?: 100, $progress_callback);
|
||||
|
||||
if (!$quiet) {
|
||||
echo "\n";
|
||||
}
|
||||
WP_CLI::line(sprintf(
|
||||
'Media download complete: %d success, %d failed out of %d total',
|
||||
$result['success'],
|
||||
@@ -293,6 +361,14 @@ class MLS_CLI {
|
||||
WP_CLI::success('No pending media to download.');
|
||||
} else {
|
||||
WP_CLI::warning('Some media failed to download.');
|
||||
$missing_count = $media_handler->get_missing_count();
|
||||
if ($missing_count > 0) {
|
||||
WP_CLI::line(sprintf(
|
||||
'Missing media log: %s (%d entries)',
|
||||
$media_handler->get_missing_log_path(),
|
||||
$missing_count
|
||||
));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -303,8 +379,14 @@ class MLS_CLI {
|
||||
}
|
||||
|
||||
WP_CLI::line("Resuming sync #{$sync_id}...");
|
||||
if (!$quiet) {
|
||||
$this->print_progress_legend($verbose);
|
||||
}
|
||||
|
||||
$result = $sync_engine->resume_sync($sync_id, $progress_callback);
|
||||
if (!$quiet) {
|
||||
echo "\n";
|
||||
}
|
||||
$this->output_sync_result($result);
|
||||
break;
|
||||
|
||||
@@ -313,6 +395,95 @@ class MLS_CLI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print progress legend
|
||||
*
|
||||
* @param bool $verbose Whether verbose mode is enabled
|
||||
*/
|
||||
private function print_progress_legend($verbose = false) {
|
||||
if (!$verbose) {
|
||||
WP_CLI::line('Legend: .=new #=updated x=deleted -=skipped !=error P=photo p=photo-skip E=photo-error |=page');
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output verbose event information
|
||||
*
|
||||
* @param string $event Event name
|
||||
* @param array $data Event data
|
||||
*/
|
||||
private function output_verbose_event($event, $data) {
|
||||
$timestamp = date('H:i:s');
|
||||
|
||||
switch ($event) {
|
||||
case 'api_request':
|
||||
WP_CLI::line("[{$timestamp}] API REQUEST: {$data['method']} {$data['url']}");
|
||||
if (!empty($data['params'])) {
|
||||
WP_CLI::line(" Params: " . json_encode($data['params']));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'api_response':
|
||||
$status = $data['success'] ? 'OK' : 'ERROR';
|
||||
WP_CLI::line("[{$timestamp}] API RESPONSE: {$status} ({$data['status_code']}) - {$data['record_count']} records, {$data['response_time']}ms");
|
||||
if (!$data['success'] && !empty($data['error'])) {
|
||||
WP_CLI::warning(" Error: {$data['error']}");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'property_created':
|
||||
$key = $data['listing_key'] ?? 'unknown';
|
||||
$address = $data['address'] ?? '';
|
||||
WP_CLI::line("[{$timestamp}] CREATED: {$key} {$address}");
|
||||
break;
|
||||
|
||||
case 'property_updated':
|
||||
$key = $data['listing_key'] ?? 'unknown';
|
||||
$address = $data['address'] ?? '';
|
||||
WP_CLI::line("[{$timestamp}] UPDATED: {$key} {$address}");
|
||||
break;
|
||||
|
||||
case 'property_deleted':
|
||||
$key = $data['listing_key'] ?? 'unknown';
|
||||
WP_CLI::line("[{$timestamp}] DELETED: {$key}");
|
||||
break;
|
||||
|
||||
case 'property_skipped':
|
||||
$key = $data['listing_key'] ?? 'unknown';
|
||||
WP_CLI::line("[{$timestamp}] SKIPPED: {$key} (dry-run)");
|
||||
break;
|
||||
|
||||
case 'property_error':
|
||||
$key = $data['listing_key'] ?? 'unknown';
|
||||
$error = $data['error'] ?? 'Unknown error';
|
||||
WP_CLI::warning("[{$timestamp}] ERROR: {$key} - {$error}");
|
||||
break;
|
||||
|
||||
case 'media_downloaded':
|
||||
$key = $data['media_key'] ?? 'unknown';
|
||||
$listing = $data['listing_key'] ?? '';
|
||||
WP_CLI::line("[{$timestamp}] MEDIA OK: {$key}" . ($listing ? " ({$listing})" : ""));
|
||||
break;
|
||||
|
||||
case 'media_skipped':
|
||||
$key = $data['media_key'] ?? 'unknown';
|
||||
WP_CLI::line("[{$timestamp}] MEDIA SKIP: {$key} (already downloaded)");
|
||||
break;
|
||||
|
||||
case 'media_error':
|
||||
$key = $data['media_key'] ?? 'unknown';
|
||||
$error = $data['error'] ?? 'Download failed';
|
||||
WP_CLI::warning("[{$timestamp}] MEDIA ERROR: {$key} - {$error}");
|
||||
break;
|
||||
|
||||
case 'page_complete':
|
||||
$processed = $data['processed'] ?? 0;
|
||||
WP_CLI::line("[{$timestamp}] PAGE COMPLETE: {$processed} records processed so far");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output sync result
|
||||
*/
|
||||
@@ -332,6 +503,17 @@ class MLS_CLI {
|
||||
$stats['deleted'],
|
||||
$stats['errors']
|
||||
));
|
||||
|
||||
// Show missing media log info if there are failures
|
||||
$media_handler = $this->plugin->get_media_handler();
|
||||
$missing_count = $media_handler->get_missing_count();
|
||||
if ($missing_count > 0) {
|
||||
WP_CLI::line(sprintf(
|
||||
'Missing media log: %s (%d entries)',
|
||||
$media_handler->get_missing_log_path(),
|
||||
$missing_count
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,7 +562,7 @@ class MLS_CLI {
|
||||
* ## OPTIONS
|
||||
*
|
||||
* <action>
|
||||
* : Action: clear, clear-listing, cleanup
|
||||
* : Action: clear, clear-listing, cleanup, missing
|
||||
*
|
||||
* [--confirm]
|
||||
* : Confirm destructive operations
|
||||
@@ -388,11 +570,20 @@ class MLS_CLI {
|
||||
* [--listing=<key>]
|
||||
* : Listing key for clear-listing
|
||||
*
|
||||
* [--clear]
|
||||
* : Clear the missing media log (for missing action)
|
||||
*
|
||||
* [--limit=<n>]
|
||||
* : Limit output lines (for missing action)
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp mls cache clear --confirm
|
||||
* wp mls cache clear-listing --listing=NST123456
|
||||
* wp mls cache cleanup
|
||||
* wp mls cache missing
|
||||
* wp mls cache missing --limit=20
|
||||
* wp mls cache missing --clear
|
||||
*
|
||||
* @subcommand cache
|
||||
*/
|
||||
@@ -421,6 +612,9 @@ class MLS_CLI {
|
||||
wp_mkdir_p($upload_dir);
|
||||
}
|
||||
|
||||
// Also clear missing log
|
||||
$media_handler->clear_missing_log();
|
||||
|
||||
WP_CLI::success('Cache cleared successfully.');
|
||||
break;
|
||||
|
||||
@@ -449,8 +643,44 @@ class MLS_CLI {
|
||||
WP_CLI::success("Cleaned up {$deleted} orphaned directories.");
|
||||
break;
|
||||
|
||||
case 'missing':
|
||||
$media_handler = $this->plugin->get_media_handler();
|
||||
$log_file = $media_handler->get_missing_log_path();
|
||||
|
||||
if (isset($assoc_args['clear'])) {
|
||||
$media_handler->clear_missing_log();
|
||||
WP_CLI::success('Missing media log cleared.');
|
||||
break;
|
||||
}
|
||||
|
||||
if (!file_exists($log_file)) {
|
||||
WP_CLI::success('No missing media logged.');
|
||||
break;
|
||||
}
|
||||
|
||||
$limit = isset($assoc_args['limit']) ? (int) $assoc_args['limit'] : null;
|
||||
$lines = file($log_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$total = count($lines);
|
||||
|
||||
WP_CLI::line(sprintf('Missing media log: %s', $log_file));
|
||||
WP_CLI::line(sprintf('Total entries: %d', $total));
|
||||
WP_CLI::line('');
|
||||
WP_CLI::line('Format: [timestamp] listing_key | media_key | error | url');
|
||||
WP_CLI::line(str_repeat('-', 80));
|
||||
|
||||
$display = $limit ? array_slice($lines, 0, $limit) : $lines;
|
||||
foreach ($display as $line) {
|
||||
WP_CLI::line($line);
|
||||
}
|
||||
|
||||
if ($limit && $total > $limit) {
|
||||
WP_CLI::line('');
|
||||
WP_CLI::line(sprintf('... and %d more entries', $total - $limit));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
WP_CLI::error("Unknown action: {$action}. Use 'clear', 'clear-listing', or 'cleanup'.");
|
||||
WP_CLI::error("Unknown action: {$action}. Use 'clear', 'clear-listing', 'cleanup', or 'missing'.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user