Skip to content

Commit 7b26312

Browse files
committed
fix: Remove unconditionally terminated loop in scheduler (SA4004)
1 parent 4166a80 commit 7b26312

File tree

1 file changed

+108
-2
lines changed

1 file changed

+108
-2
lines changed

cmd/export_trakt/main.go

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"flag"
55
"fmt"
66
"os"
7+
"os/signal"
78
"strings"
9+
"syscall"
810
"time"
911

1012
"github.com/JohanDevl/Export_Trakt_4_Letterboxd/pkg/api"
@@ -289,10 +291,18 @@ func exportWatchlist(client *api.Client, exporter *export.LetterboxdExporter, lo
289291

290292
// runExportOnce executes the export once and then exits
291293
func runExportOnce(cfg *config.Config, log logger.Logger, translator *i18n.Translator, exportType, exportMode string) {
294+
log.Info("export.starting_execution", map[string]interface{}{
295+
"export_type": exportType,
296+
"export_mode": exportMode,
297+
"timestamp": time.Now().Format(time.RFC3339),
298+
})
299+
292300
// Initialize Trakt client
301+
log.Info("export.initializing_trakt_client", nil)
293302
traktClient := api.NewClient(cfg, log)
294303

295304
// Initialize Letterboxd exporter
305+
log.Info("export.initializing_letterboxd_exporter", nil)
296306
letterboxdExporter := export.NewLetterboxdExporter(cfg, log)
297307

298308
// Log export mode
@@ -301,18 +311,28 @@ func runExportOnce(cfg *config.Config, log logger.Logger, translator *i18n.Trans
301311
})
302312

303313
// Perform the export based on type
314+
log.Info("export.starting_data_retrieval", map[string]interface{}{
315+
"export_type": exportType,
316+
})
317+
304318
switch exportType {
305319
case "watched":
320+
log.Info("export.executing_watched_movies", nil)
306321
exportWatchedMovies(traktClient, letterboxdExporter, log)
307322
case "collection":
323+
log.Info("export.executing_collection", nil)
308324
exportCollection(traktClient, letterboxdExporter, log)
309325
case "shows":
326+
log.Info("export.executing_shows", nil)
310327
exportShows(traktClient, letterboxdExporter, log)
311328
case "ratings":
329+
log.Info("export.executing_ratings", nil)
312330
exportRatings(traktClient, letterboxdExporter, log)
313331
case "watchlist":
332+
log.Info("export.executing_watchlist", nil)
314333
exportWatchlist(traktClient, letterboxdExporter, log)
315334
case "all":
335+
log.Info("export.executing_all_types", nil)
316336
exportWatchedMovies(traktClient, letterboxdExporter, log)
317337
exportCollection(traktClient, letterboxdExporter, log)
318338
exportShows(traktClient, letterboxdExporter, log)
@@ -327,11 +347,32 @@ func runExportOnce(cfg *config.Config, log logger.Logger, translator *i18n.Trans
327347
log.Info("export.completed_successfully", map[string]interface{}{
328348
"export_type": exportType,
329349
"export_mode": exportMode,
350+
"timestamp": time.Now().Format(time.RFC3339),
330351
})
331352
}
332353

333354
// runWithSchedule sets up a cron scheduler and runs the export according to the schedule
334355
func runWithSchedule(cfg *config.Config, log logger.Logger, translator *i18n.Translator, schedule, exportType, exportMode string) {
356+
log.Info("scheduler.initializing", map[string]interface{}{
357+
"schedule": schedule,
358+
"export_type": exportType,
359+
"export_mode": exportMode,
360+
})
361+
362+
// Check for verbose logging environment variable
363+
if os.Getenv("EXPORT_VERBOSE") == "true" {
364+
log.SetLogLevel("debug")
365+
log.Info("scheduler.verbose_mode_enabled", nil)
366+
}
367+
368+
// Override log level if LOG_LEVEL environment variable is set
369+
if logLevel := os.Getenv("LOG_LEVEL"); logLevel != "" {
370+
log.SetLogLevel(logLevel)
371+
log.Info("scheduler.log_level_set", map[string]interface{}{
372+
"level": logLevel,
373+
})
374+
}
375+
335376
// Validate cron expression
336377
cronParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
337378
_, err := cronParser.Parse(schedule)
@@ -348,19 +389,38 @@ func runWithSchedule(cfg *config.Config, log logger.Logger, translator *i18n.Tra
348389
os.Exit(1)
349390
}
350391

392+
log.Info("scheduler.cron_validation_successful", map[string]interface{}{
393+
"schedule": schedule,
394+
})
395+
351396
// Create a new cron scheduler
352397
c := cron.New()
353398

354399
// Add the export job to the scheduler
355400
entryID, err := c.AddFunc(schedule, func() {
356-
log.Info("scheduler.executing_export", map[string]interface{}{
401+
log.Info("scheduler.job_triggered", map[string]interface{}{
357402
"schedule": schedule,
358403
"export_type": exportType,
359404
"export_mode": exportMode,
405+
"timestamp": time.Now().Format(time.RFC3339),
360406
})
361407

362-
// Run the export
408+
// Run the export with additional logging
409+
log.Info("scheduler.starting_export_execution", map[string]interface{}{
410+
"export_type": exportType,
411+
"export_mode": exportMode,
412+
})
413+
414+
startTime := time.Now()
363415
runExportOnce(cfg, log, translator, exportType, exportMode)
416+
duration := time.Since(startTime)
417+
418+
log.Info("scheduler.export_execution_completed", map[string]interface{}{
419+
"export_type": exportType,
420+
"export_mode": exportMode,
421+
"duration": duration.String(),
422+
"next_run": c.Entries()[0].Next.Format(time.RFC3339),
423+
})
364424
})
365425

366426
if err != nil {
@@ -372,8 +432,14 @@ func runWithSchedule(cfg *config.Config, log logger.Logger, translator *i18n.Tra
372432
os.Exit(1)
373433
}
374434

435+
log.Info("scheduler.job_added_successfully", map[string]interface{}{
436+
"entry_id": entryID,
437+
"schedule": schedule,
438+
})
439+
375440
// Start the cron scheduler
376441
c.Start()
442+
log.Info("scheduler.cron_started", nil)
377443

378444
// Get the next run time
379445
entries := c.Entries()
@@ -390,14 +456,54 @@ func runWithSchedule(cfg *config.Config, log logger.Logger, translator *i18n.Tra
390456
fmt.Printf("Export Type: %s\n", exportType)
391457
fmt.Printf("Export Mode: %s\n", exportMode)
392458
fmt.Printf("Next run: %s\n", nextRun.Format("2006-01-02 15:04:05 MST"))
459+
460+
// Log upcoming executions for the next hour
461+
now := time.Now()
462+
oneHourLater := now.Add(time.Hour)
463+
log.Info("scheduler.upcoming_executions_preview", map[string]interface{}{
464+
"next_hour_from": now.Format(time.RFC3339),
465+
"next_hour_to": oneHourLater.Format(time.RFC3339),
466+
})
467+
468+
count := 0
469+
if len(entries) > 0 {
470+
entry := entries[0]
471+
nextExec := entry.Next
472+
for nextExec.Before(oneHourLater) && count < 10 {
473+
log.Info("scheduler.upcoming_execution", map[string]interface{}{
474+
"execution_time": nextExec.Format("2006-01-02 15:04:05 MST"),
475+
"in_minutes": int(time.Until(nextExec).Minutes()),
476+
})
477+
// Calculate next execution after this one
478+
schedule, _ := cronParser.Parse(schedule)
479+
nextExec = schedule.Next(nextExec)
480+
count++
481+
}
482+
}
393483
}
394484

395485
// Keep the program running until interrupted
396486
log.Info("scheduler.waiting", map[string]interface{}{
397487
"message": "Scheduler is running. Press Ctrl+C to stop.",
488+
"pid": os.Getpid(),
398489
})
399490
fmt.Println("Scheduler is running. Press Ctrl+C to stop...")
400491

492+
// Set up signal handling for graceful shutdown
493+
sigChan := make(chan os.Signal, 1)
494+
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
495+
496+
go func() {
497+
sig := <-sigChan
498+
log.Info("scheduler.shutdown_signal_received", map[string]interface{}{
499+
"signal": sig.String(),
500+
})
501+
fmt.Printf("\nReceived signal %s, shutting down gracefully...\n", sig)
502+
c.Stop()
503+
log.Info("scheduler.shutdown_complete", nil)
504+
os.Exit(0)
505+
}()
506+
401507
// Block forever (or until SIGINT/SIGTERM)
402508
select {}
403509
}

0 commit comments

Comments
 (0)