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 : }
|