2
2
// - [x] Setup local sync bucket
3
3
// - [x] Import local buckets and sync events from aw-server (either through API or through creating a read-only Datastore)
4
4
// - [x] Import buckets and sync events from remotes
5
- // - [ ] Add CLI arguments
5
+ // - [x ] Add CLI arguments
6
6
// - [x] For which local server to use
7
7
// - [x] For which sync dir to use
8
- // - [ ] Date to start syncing from
8
+ // - [x ] Date to start syncing from
9
9
10
10
#[ macro_use]
11
11
extern crate log;
@@ -60,35 +60,45 @@ struct Opts {
60
60
enum Commands {
61
61
/// Daemon subcommand
62
62
/// Starts aw-sync as a daemon, which will sync every 5 minutes.
63
- Daemon { } ,
63
+ Daemon {
64
+ /// Date to start syncing from.
65
+ /// If not specified, start from beginning.
66
+ /// Format: YYYY-MM-DD
67
+ #[ clap( long, value_parser=parse_start_date) ]
68
+ start_date : Option < DateTime < Utc > > ,
69
+
70
+ /// Specify buckets to sync using a comma-separated list.
71
+ /// By default, all buckets are synced.
72
+ #[ clap( long) ]
73
+ buckets : Option < String > ,
74
+
75
+ /// Full path to sync db file
76
+ /// Useful for syncing buckets from a specific db file in the sync directory.
77
+ /// Must be a valid absolute path to a file in the sync directory.
78
+ #[ clap( long) ]
79
+ sync_db : Option < PathBuf > ,
80
+ } ,
64
81
65
- /// Sync subcommand (basic)
82
+ /// Sync subcommand
66
83
///
67
- /// Pulls remote buckets then pushes local buckets.
84
+ /// Syncs data between local aw-server and sync directory.
85
+ /// First pulls remote buckets from the sync directory to the local aw-server.
86
+ /// Then pushes local buckets from the aw-server to the local sync directory.
68
87
Sync {
69
88
/// Host(s) to pull from, comma separated. Will pull from all hosts if not specified.
70
89
#[ clap( long, value_parser=parse_list) ]
71
90
host : Option < Vec < String > > ,
72
- } ,
73
91
74
- /// Sync subcommand (advanced)
75
- ///
76
- /// Pulls remote buckets then pushes local buckets.
77
- /// First pulls remote buckets in the sync directory to the local aw-server.
78
- /// Then pushes local buckets from the aw-server to the local sync directory.
79
- #[ clap( arg_required_else_help = true ) ]
80
- SyncAdvanced {
81
92
/// Date to start syncing from.
82
93
/// If not specified, start from beginning.
83
- /// NOTE: might be unstable, as count cannot be used to verify integrity of sync.
84
94
/// Format: YYYY-MM-DD
85
95
#[ clap( long, value_parser=parse_start_date) ]
86
96
start_date : Option < DateTime < Utc > > ,
87
97
88
98
/// Specify buckets to sync using a comma-separated list.
89
- /// If not specified , all buckets will be synced.
90
- #[ clap( long, value_parser=parse_list ) ]
91
- buckets : Option < Vec < String > > ,
99
+ /// By default , all buckets are synced.
100
+ #[ clap( long) ]
101
+ buckets : Option < String > ,
92
102
93
103
/// Mode to sync in. Can be "push", "pull", or "both".
94
104
/// Defaults to "both".
@@ -111,6 +121,13 @@ fn parse_start_date(arg: &str) -> Result<DateTime<Utc>, chrono::ParseError> {
111
121
}
112
122
113
123
fn parse_list ( arg : & str ) -> Result < Vec < String > , clap:: Error > {
124
+ // If the argument is empty or just whitespace, return an empty Vec
125
+ // This handles the case when --buckets is used without a value
126
+ if arg. trim ( ) . is_empty ( ) {
127
+ return Ok ( vec ! [ ] ) ;
128
+ }
129
+
130
+ // Otherwise, split by comma as usual
114
131
Ok ( arg. split ( ',' ) . map ( |s| s. to_string ( ) ) . collect ( ) )
115
132
}
116
133
@@ -139,60 +156,94 @@ fn main() -> Result<(), Box<dyn Error>> {
139
156
140
157
let client = AwClient :: new ( & opts. host , port, "aw-sync" ) ?;
141
158
142
- // if opts.command is None, then we're using the default subcommand (Sync)
143
- match opts. command . unwrap_or ( Commands :: Daemon { } ) {
159
+ // if opts.command is None, then we're using the default subcommand (Daemon)
160
+ match opts. command . unwrap_or ( Commands :: Daemon {
161
+ start_date : None ,
162
+ buckets : None ,
163
+ sync_db : None ,
164
+ } ) {
144
165
// Start daemon
145
- Commands :: Daemon { } => {
166
+ Commands :: Daemon {
167
+ start_date,
168
+ buckets,
169
+ sync_db,
170
+ } => {
146
171
info ! ( "Starting daemon..." ) ;
147
- daemon ( & client) ?;
148
- }
149
- // Perform basic sync
150
- Commands :: Sync { host } => {
151
- // Pull
152
- match host {
153
- Some ( hosts) => {
154
- for host in hosts. iter ( ) {
155
- info ! ( "Pulling from host: {}" , host) ;
156
- sync_wrapper:: pull ( host, & client) ?;
157
- }
158
- }
159
- None => {
160
- info ! ( "Pulling from all hosts" ) ;
161
- sync_wrapper:: pull_all ( & client) ?;
162
- }
163
- }
164
172
165
- // Push
166
- info ! ( "Pushing local data" ) ;
167
- sync_wrapper:: push ( & client) ?
173
+ // Use an empty vector to sync all buckets for these cases:
174
+ // 1. When --buckets '*' is supplied
175
+ // 2. When no bucket argument is provided (default)
176
+ let effective_buckets = if buckets. as_deref ( ) == Some ( "*" ) || buckets. is_none ( ) {
177
+ Some ( vec ! [ ] )
178
+ } else if let Some ( buckets_str) = buckets {
179
+ Some ( buckets_str. split ( ',' ) . map ( |s| s. to_string ( ) ) . collect ( ) )
180
+ } else {
181
+ None
182
+ } ;
183
+
184
+ daemon ( & client, start_date, effective_buckets, sync_db) ?;
168
185
}
169
- // Perform two-way sync
170
- Commands :: SyncAdvanced {
186
+ // Perform sync
187
+ Commands :: Sync {
188
+ host,
171
189
start_date,
172
190
buckets,
173
191
mode,
174
192
sync_db,
175
193
} => {
176
- let sync_dir = dirs:: get_sync_dir ( ) ?;
177
- if let Some ( db_path) = & sync_db {
178
- info ! ( "Using sync db: {}" , & db_path. display( ) ) ;
194
+ // Use an empty vector to sync all buckets for these cases:
195
+ // 1. When --buckets '*' is supplied
196
+ // 2. When no bucket argument is provided (default)
197
+ let effective_buckets = if buckets. as_deref ( ) == Some ( "*" ) || buckets. is_none ( ) {
198
+ Some ( vec ! [ ] )
199
+ } else if let Some ( buckets_str) = buckets {
200
+ Some ( buckets_str. split ( ',' ) . map ( |s| s. to_string ( ) ) . collect ( ) )
201
+ } else {
202
+ None
203
+ } ;
179
204
180
- if !db_path. is_absolute ( ) {
181
- Err ( "Sync db path must be absolute" ) ?
182
- }
183
- if !db_path. starts_with ( & sync_dir) {
184
- Err ( "Sync db path must be in sync directory" ) ?
205
+ // If advanced options are provided, use advanced sync mode
206
+ if start_date. is_some ( ) || effective_buckets. is_some ( ) || sync_db. is_some ( ) {
207
+ let sync_dir = dirs:: get_sync_dir ( ) ?;
208
+ if let Some ( db_path) = & sync_db {
209
+ info ! ( "Using sync db: {}" , & db_path. display( ) ) ;
210
+
211
+ if !db_path. is_absolute ( ) {
212
+ Err ( "Sync db path must be absolute" ) ?
213
+ }
214
+ if !db_path. starts_with ( & sync_dir) {
215
+ Err ( "Sync db path must be in sync directory" ) ?
216
+ }
185
217
}
186
- }
187
218
188
- let sync_spec = sync:: SyncSpec {
189
- path : sync_dir,
190
- path_db : sync_db,
191
- buckets,
192
- start : start_date,
193
- } ;
219
+ let sync_spec = sync:: SyncSpec {
220
+ path : sync_dir,
221
+ path_db : sync_db,
222
+ buckets : effective_buckets,
223
+ start : start_date,
224
+ } ;
225
+
226
+ sync:: sync_run ( & client, & sync_spec, mode) ?
227
+ } else {
228
+ // Simple host-based sync mode (backwards compatibility)
229
+ // Pull
230
+ match host {
231
+ Some ( hosts) => {
232
+ for host in hosts. iter ( ) {
233
+ info ! ( "Pulling from host: {}" , host) ;
234
+ sync_wrapper:: pull ( host, & client) ?;
235
+ }
236
+ }
237
+ None => {
238
+ info ! ( "Pulling from all hosts" ) ;
239
+ sync_wrapper:: pull_all ( & client) ?;
240
+ }
241
+ }
194
242
195
- sync:: sync_run ( & client, & sync_spec, mode) ?
243
+ // Push
244
+ info ! ( "Pushing local data" ) ;
245
+ sync_wrapper:: push ( & client) ?
246
+ }
196
247
}
197
248
198
249
// List all buckets
@@ -207,23 +258,45 @@ fn main() -> Result<(), Box<dyn Error>> {
207
258
Ok ( ( ) )
208
259
}
209
260
210
- fn daemon ( client : & AwClient ) -> Result < ( ) , Box < dyn Error > > {
261
+ fn daemon (
262
+ client : & AwClient ,
263
+ start_date : Option < DateTime < Utc > > ,
264
+ buckets : Option < Vec < String > > ,
265
+ sync_db : Option < PathBuf > ,
266
+ ) -> Result < ( ) , Box < dyn Error > > {
211
267
let ( tx, rx) = channel ( ) ;
212
268
213
269
ctrlc:: set_handler ( move || {
214
270
let _ = tx. send ( ( ) ) ;
215
271
} ) ?;
216
272
273
+ let sync_dir = dirs:: get_sync_dir ( ) ?;
274
+ if let Some ( db_path) = & sync_db {
275
+ info ! ( "Using sync db: {}" , & db_path. display( ) ) ;
276
+
277
+ if !db_path. is_absolute ( ) {
278
+ Err ( "Sync db path must be absolute" ) ?
279
+ }
280
+ if !db_path. starts_with ( & sync_dir) {
281
+ Err ( "Sync db path must be in sync directory" ) ?
282
+ }
283
+ }
284
+
285
+ let sync_spec = sync:: SyncSpec {
286
+ path : sync_dir,
287
+ buckets,
288
+ path_db : sync_db,
289
+ start : start_date,
290
+ } ;
291
+
217
292
loop {
218
- if let Err ( e) = daemon_sync_cycle ( client) {
293
+ if let Err ( e) = sync :: sync_run ( client, & sync_spec , sync :: SyncMode :: Both ) {
219
294
error ! ( "Error during sync cycle: {}" , e) ;
220
- // Re-throw the error
221
295
return Err ( e) ;
222
296
}
223
297
224
298
info ! ( "Sync pass done, sleeping for 5 minutes" ) ;
225
299
226
- // Wait for either the sleep duration or a termination signal
227
300
match rx. recv_timeout ( Duration :: from_secs ( 300 ) ) {
228
301
Ok ( _) | Err ( RecvTimeoutError :: Disconnected ) => {
229
302
info ! ( "Termination signal received, shutting down." ) ;
@@ -237,13 +310,3 @@ fn daemon(client: &AwClient) -> Result<(), Box<dyn Error>> {
237
310
238
311
Ok ( ( ) )
239
312
}
240
-
241
- fn daemon_sync_cycle ( client : & AwClient ) -> Result < ( ) , Box < dyn Error > > {
242
- info ! ( "Pulling from all hosts" ) ;
243
- sync_wrapper:: pull_all ( client) ?;
244
-
245
- info ! ( "Pushing local data" ) ;
246
- sync_wrapper:: push ( client) ?;
247
-
248
- Ok ( ( ) )
249
- }
0 commit comments