LCOV - code coverage report
Current view: top level - src - main_sync.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 80.0 % 115 92
Test Date: 2026-05-07 15:53:08 Functions: 100.0 % 3 3

            Line data    Source code
       1              : /**
       2              :  * @file main_sync.c
       3              :  * @brief Entry point for email-sync — background sync binary with cron management.
       4              :  *
       5              :  * email-sync downloads all messages from all IMAP folders to the local store.
       6              :  * It also manages its own cron scheduling via the 'cron' subcommand.
       7              :  *
       8              :  * Typical crontab entry (installed by 'email-sync cron setup'):
       9              :  *   *\/15 * * * * /path/to/email-sync >> ~/.cache/email-cli/sync.log 2>&1
      10              :  */
      11              : 
      12              : #include <stdio.h>
      13              : #include <stdlib.h>
      14              : #include <string.h>
      15              : #include <locale.h>
      16              : #include "config_store.h"
      17              : #include "email_service.h"
      18              : #include "platform/path.h"
      19              : #include "raii.h"
      20              : #include "logger.h"
      21              : #include "local_store.h"
      22              : #include "fs_util.h"
      23              : 
      24            2 : static void help(void) {
      25            2 :     printf(
      26              :         "Usage: email-sync [--account <email>] [--rebuild-index] [--apply-rules]\n"
      27              :         "                  [--rebuild-contacts] [--reconcile] [--verbose]\n"
      28              :         "       email-sync cron <setup|remove|status>\n"
      29              :         "\n"
      30              :         "Downloads all messages from all accounts to the local store.\n"
      31              :         "Without --account, every configured account is synced in alphabetical\n"
      32              :         "order.  With --account, only the specified account is synced.\n"
      33              :         "Messages already stored locally are skipped.\n"
      34              :         "\n"
      35              :         "Subcommands:\n"
      36              :         "  cron setup    Install a crontab entry to run email-sync periodically.\n"
      37              :         "                Uses SYNC_INTERVAL from config (default: 5 minutes).\n"
      38              :         "  cron remove   Remove the email-sync crontab entry.\n"
      39              :         "  cron status   Show whether an automatic sync entry is installed.\n"
      40              :         "\n"
      41              :         "Options:\n"
      42              :         "  --account <email>   Sync only the account with this email address\n"
      43              :         "  --rebuild-index     Rebuild label index files from cached .hdr files\n"
      44              :         "                      (Gmail only; does not re-download messages)\n"
      45              :         "  --apply-rules       Apply mail sorting rules from rules.ini to all\n"
      46              :         "                      locally cached messages (retroactive; no download)\n"
      47              :         "  --rebuild-contacts  Rebuild contacts.tsv from all cached message headers\n"
      48              :         "                      (no download; useful after first-time bulk sync)\n"
      49              :         "  --reconcile         Force a full server reconcile for Gmail accounts\n"
      50              :         "                      (bypass the incremental fast path; use when the\n"
      51              :         "                      automatic sync seems stuck or out of sync)\n"
      52              :         "  --verbose, -v       Print per-rule firing details during sync or\n"
      53              :         "                      --apply-rules\n"
      54              :         "  --help, -h          Show this help message\n"
      55              :         "\n"
      56              :         "Exit Codes:\n"
      57              :         "  0   Completed successfully (or already up to date)\n"
      58              :         "  1   Error (no config, connection failure, etc.)\n"
      59              :     );
      60            2 : }
      61              : 
      62            2 : static void help_cron(void) {
      63            2 :     printf(
      64              :         "Usage: email-sync cron <setup|remove|status>\n"
      65              :         "\n"
      66              :         "Manages automatic background synchronisation via user crontab.\n"
      67              :         "No sudo or system-level access is required.\n"
      68              :         "\n"
      69              :         "Subcommands:\n"
      70              :         "  setup    Install a crontab entry to run email-sync periodically.\n"
      71              :         "           Uses SYNC_INTERVAL from config (default: 5 minutes if not set).\n"
      72              :         "           Saves the interval to config if a default was applied.\n"
      73              :         "  remove   Remove the email-sync crontab entry.\n"
      74              :         "  status   Show whether an automatic sync entry is currently installed.\n"
      75              :         "\n"
      76              :         "Examples:\n"
      77              :         "  email-sync cron setup\n"
      78              :         "  email-sync cron status\n"
      79              :         "  email-sync cron remove\n"
      80              :     );
      81            2 : }
      82              : 
      83              : #ifndef EMAIL_CLI_VERSION
      84              : #define EMAIL_CLI_VERSION "unknown"
      85              : #endif
      86              : 
      87           60 : int main(int argc, char *argv[]) {
      88           60 :     setlocale(LC_ALL, "");
      89              : 
      90           60 :     const char *cmd = argc > 1 ? argv[1] : NULL;
      91              : 
      92           60 :     if (cmd && (strcmp(cmd, "--version") == 0 || strcmp(cmd, "-V") == 0)) {
      93            1 :         printf("email-sync %s\n", EMAIL_CLI_VERSION);
      94            1 :         return EXIT_SUCCESS;
      95              :     }
      96              : 
      97              :     /* Handle --help / -h */
      98           59 :     if (cmd && (strcmp(cmd, "--help") == 0 || strcmp(cmd, "-h") == 0)) {
      99            2 :         help();
     100            2 :         return EXIT_SUCCESS;
     101              :     }
     102              : 
     103              :     /* cron status and remove don't need config — handle early */
     104           57 :     if (cmd && strcmp(cmd, "cron") == 0) {
     105            5 :         const char *subcmd = argc > 2 ? argv[2] : "";
     106            5 :         if (strcmp(subcmd, "--help") == 0 || strcmp(subcmd, "-h") == 0) {
     107            2 :             help_cron();
     108            2 :             return EXIT_SUCCESS;
     109              :         }
     110            3 :         if (strcmp(subcmd, "status") == 0)
     111            1 :             return email_service_cron_status() == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
     112            2 :         if (strcmp(subcmd, "remove") == 0)
     113            1 :             return email_service_cron_remove() == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
     114            1 :         if (strcmp(subcmd, "setup") != 0) {
     115            1 :             fprintf(stderr, "Usage: email-sync cron <setup|remove|status>\n");
     116            1 :             return EXIT_FAILURE;
     117              :         }
     118              :         /* 'cron setup' falls through — needs config */
     119              :     }
     120              : 
     121              :     /* Determine cache directory for logs */
     122           52 :     const char *cache_base = platform_cache_dir();
     123           52 :     if (!cache_base) {
     124            0 :         fprintf(stderr, "Fatal: Could not determine cache directory.\n");
     125            0 :         return EXIT_FAILURE;
     126              :     }
     127              : 
     128           52 :     RAII_STRING char *log_dir  = NULL;
     129           52 :     RAII_STRING char *log_file = NULL;
     130          104 :     if (asprintf(&log_dir,  "%s/email-cli/logs", cache_base) == -1 ||
     131           52 :         asprintf(&log_file, "%s/sync-session.log", log_dir)  == -1) {
     132            0 :         fprintf(stderr, "Fatal: Memory allocation failed.\n");
     133            0 :         return EXIT_FAILURE;
     134              :     }
     135              : 
     136              :     /* Initialize logger */
     137           52 :     if (fs_mkdir_p(log_dir, 0700) != 0)
     138            0 :         fprintf(stderr, "Warning: Could not create log directory %s\n", log_dir);
     139           52 :     if (logger_init(log_file, LOG_DEBUG) != 0)
     140            0 :         fprintf(stderr, "Warning: Logging system failed to initialize.\n");
     141              : 
     142              :     /* 'cron setup' needs config for sync_interval */
     143           52 :     if (cmd && strcmp(cmd, "cron") == 0) {
     144            0 :         logger_log(LOG_INFO, "--- email-sync cron setup ---");
     145            0 :         Config *cfg = config_load_from_store();
     146            0 :         if (!cfg) {
     147            0 :             fprintf(stderr, "Error: No configuration found. Run 'email-cli config' or "
     148              :                             "the setup wizard first.\n");
     149            0 :             logger_close();
     150            0 :             return EXIT_FAILURE;
     151              :         }
     152            0 :         if (cfg->sync_interval <= 0) {
     153            0 :             cfg->sync_interval = 5;
     154            0 :             printf("sync_interval not configured; using default of 5 minutes.\n");
     155            0 :             if (config_save_to_store(cfg) != 0)
     156            0 :                 fprintf(stderr, "Warning: could not save sync_interval to config.\n");
     157              :         }
     158            0 :         int rc = email_service_cron_setup(cfg);
     159            0 :         config_free(cfg);
     160            0 :         logger_close();
     161            0 :         return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
     162              :     }
     163              : 
     164           52 :     logger_log(LOG_INFO, "--- email-sync starting ---");
     165              : 
     166              :     /* Parse remaining options (sync mode) */
     167           52 :     const char *account_filter    = NULL;
     168           52 :     int do_rebuild_index    = 0;
     169           52 :     int do_apply_rules      = 0;
     170           52 :     int do_rebuild_contacts = 0;
     171           52 :     int do_force_reconcile  = 0;
     172           52 :     int do_verbose          = 0;
     173           90 :     for (int i = 1; i < argc; i++) {
     174           40 :         if (strcmp(argv[i], "--account") == 0) {
     175           25 :             if (i + 1 >= argc) {
     176            1 :                 fprintf(stderr, "Error: --account requires an email address.\n");
     177            1 :                 logger_close();
     178            1 :                 return EXIT_FAILURE;
     179              :             }
     180           24 :             account_filter = argv[++i];
     181           24 :             continue;
     182              :         }
     183           15 :         if (strcmp(argv[i], "--rebuild-index") == 0) {
     184            8 :             do_rebuild_index = 1;
     185            8 :             continue;
     186              :         }
     187            7 :         if (strcmp(argv[i], "--apply-rules") == 0) {
     188            2 :             do_apply_rules = 1;
     189            2 :             continue;
     190              :         }
     191            5 :         if (strcmp(argv[i], "--rebuild-contacts") == 0) {
     192            1 :             do_rebuild_contacts = 1;
     193            1 :             continue;
     194              :         }
     195            4 :         if (strcmp(argv[i], "--reconcile") == 0) {
     196            1 :             do_force_reconcile = 1;
     197            1 :             continue;
     198              :         }
     199            3 :         if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) {
     200            2 :             do_verbose = 1;
     201            2 :             continue;
     202              :         }
     203            1 :         fprintf(stderr, "Unknown option '%s'.\nRun 'email-sync --help' for usage.\n",
     204            1 :                 argv[i]);
     205            1 :         logger_close();
     206            1 :         return EXIT_FAILURE;
     207              :     }
     208              : 
     209              :     /* Run requested operation */
     210           50 :     if (do_rebuild_index) {
     211            8 :         int rc = email_service_rebuild_indexes(account_filter);
     212            8 :         logger_close();
     213            8 :         return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
     214              :     }
     215              : 
     216           42 :     if (do_apply_rules) {
     217            2 :         email_service_set_verbose(do_verbose);
     218            2 :         int rc = email_service_apply_rules(account_filter, 0, do_verbose);
     219            2 :         if (rc >= 0) {
     220              :             /* Immediately sync back to push rule-fired changes to the server */
     221            2 :             printf("\nSyncing rule changes to server...\n");
     222            2 :             email_service_sync_all(account_filter, 0);
     223              :         }
     224            2 :         logger_close();
     225            2 :         return rc >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
     226              :     }
     227              : 
     228           40 :     if (do_rebuild_contacts) {
     229            1 :         int rc = email_service_rebuild_contacts(account_filter);
     230            1 :         logger_close();
     231            1 :         return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
     232              :     }
     233              : 
     234              :     /* Normal sync */
     235           39 :     email_service_set_verbose(do_verbose);
     236           39 :     int result = email_service_sync_all(account_filter, do_force_reconcile);
     237              : 
     238           39 :     logger_log(LOG_INFO, "--- email-sync finished (result: %d) ---", result);
     239           39 :     logger_close();
     240              : 
     241           39 :     if (result >= 0)
     242           39 :         return EXIT_SUCCESS;
     243            0 :     fprintf(stderr, "\nSync failed. Check logs in %s\n", log_file);
     244            0 :     return EXIT_FAILURE;
     245              : }
        

Generated by: LCOV version 2.0-1