Skip to content
API Documentation
BastionGuard™
RsyncTask.vala
Go to the documentation of this file.
1/*
2 * RsyncTask.vala
3 *
4 * Copyright 2012-2018 Tony George <teejeetech@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 *
21 *
22 */
23
24using TeeJee.Logging;
28using TeeJee.System;
29using TeeJee.Misc;
30
31public class RsyncTask : AsyncTask{
32
33 private enum RegexType {
34 Status,
35 Created,
36 LogCreated,
37 Deleted,
38 LogDeleted,
39 Modified,
40 LogModified,
41 Unchanged,
42 LogUnchanged,
43 TotalSize,
44
45 Count
46 }
47
48 // settings
49 public bool delete_extra = true;
50 public bool delete_after = false;
51 public bool delete_excluded = false;
52 public bool relative = false;
53
54 public string rsync_log_file = "";
55 public string exclude_from_file = "";
56 public string link_from_path = "";
57 public string source_path = "";
58 public string dest_path = "";
59 public bool verbose = true;
60 public bool io_nice = true;
61 public bool dry_run = false;
62
63 // regex
64 private Regex[] regex_list = null;
65
66 // status
67 public GLib.Queue<string> status_lines;
68 public int64 status_line_count = 0;
69 public int64 total_size = 0;
70
71 public int64 count_created;
72 public int64 count_deleted;
73 public int64 count_modified;
74 public int64 count_checksum;
75 public int64 count_size;
76 public int64 count_timestamp;
77 public int64 count_permissions;
78 public int64 count_owner;
79 public int64 count_group;
80 public int64 count_unchanged;
81
82 public StringBuilder log;
83
84 public RsyncTask(){
86 status_lines = new GLib.Queue<string>();
87 }
88
90 if (regex_list != null){
91 return; // already initialized
92 }
93
94 regex_list = new Regex[RegexType.Count];
95
96 /*
97 2018/02/03 16:53:46 [2419] >f..t...... boot/grub/grubenv
98 >f.........
99 <dcstpoguax
100 cL
101 hD
102 .S
103 *
104 columns-1:
105 < > means that a file is being transferred
106 c means that a local change/creation is occurring for the item
107 h means that the item is a hard link to another item
108 . means that the item is not being updated (though it might have attributes that are being modified).
109 * means that the rest of the itemized-output area contains a message (e.g. "deleting").
110
111 columns-2:
112 f file, d directory, L symlink, D device, S special file (e.g. named sockets and fifos).
113
114 columns-3-11:
115 c regular file has different checksum or that symlink, device, or special file has changed value
116 s size of a regular file is different and is being updated
117 t modification time is different and is being updated
118 p permissions are different and is being updated
119 o owner is different and is being updated
120 g group is different and is being updated
121 u reserved for future use.
122 a ACL information changed.
123 x extended attribute information changed.
124 */
125
126 try {
127 //Example: status=-1
128 regex_list[RegexType.Status] = new Regex(
129 """([<>ch.*])([.fdLDS])(c|\+|\.| )(s|\+|\.| )(t|\+|\.| )(p|\+|\.| )(o|\+|\.| )(g|\+|\.| )(u|\+|\.| )(a|\+|\.| )(x|\+|\.| ) (.*)""");
130
131 regex_list[RegexType.Created] = new Regex(
132 """([<>ch.*])([.fdLDS])[+]{9} (.*)""");
133
134 regex_list[RegexType.LogCreated] = new Regex(
135 """[0-9\/]+ [0-9:.]+ \‍[[0-9]+\‍] ([<>ch.*])([.fdLDS])[+]{9} (.*)""");
136
137 regex_list[RegexType.Deleted] = new Regex(
138 """\*deleting[ \t]+(.*)""");
139
140 regex_list[RegexType.LogDeleted] = new Regex(
141 """[0-9\/]+ [0-9:.]+ \‍[[0-9]+\‍] \*deleting[ \t]+(.*)""");
142
143 regex_list[RegexType.Modified] = new Regex(
144 """([<>ch.])([.fdLDS])(c|\+|\.| )(s|\+|\.| )(t|\+|\.| )(p|\+|\.| )(o|\+|\.| )(g|\+|\.| )(u|\+|\.| )(a|\+|\.| )(x|\+|\.) (.*)""");
145
146 regex_list[RegexType.LogModified] = new Regex(
147 """[0-9\/]+ [0-9:.]+ \‍[[0-9]+\‍] ([<>ch.])([.fdLDS])(c|\+|\.| )(s|\+|\.| )(t|\+|\.| )(p|\+|\.| )(o|\+|\.| )(g|\+|\.| )(u|\+|\.| )(a|\+|\.| )(x|\+|\.) (.*)""");
148
149 regex_list[RegexType.Unchanged] = new Regex(
150 """([.h])([.fdLDS])[ ]{9} (.*)""");
151
152 regex_list[RegexType.LogUnchanged] = new Regex(
153 """[0-9\/]+ [0-9:.]+ \‍[[0-9]+\‍] ([.h])([.fdLDS])[ ]{9} (.*)""");
154
155 regex_list[RegexType.TotalSize] = new Regex(
156 """total size is ([0-9,]+)[ \t]+speedup is [0-9.]+""");
157
158 }
159 catch (Error e) {
160 log_error (e.message);
161 }
162 }
163
164 public override void prepare() {
165 base.prepare();
166
167 //status_lines = new GLib.Queue<string>();
169 total_size = 0;
170
171 count_created = 0;
172 count_deleted = 0;
173 count_modified = 0;
174 count_checksum = 0;
175 count_size = 0;
176 count_timestamp = 0;
178 count_owner = 0;
179 count_group = 0;
180 count_unchanged = 0;
181
182 log = new StringBuilder();
183 }
184
185 protected override string build_script() {
186
187 var cmd = "export LC_ALL=C.UTF-8\n";
188
189 if (io_nice){
190 //cmd += "ionice -c2 -n7 ";
191 }
192
193 cmd += "rsync -aii";
194
195 //if (!dry_run){
196 // cmd += "i";
197 //}
198
199 cmd += " --recursive";
200
201 if (verbose){
202 cmd += " --verbose";
203 }
204 else{
205 cmd += " --quiet";
206 }
207
208 if (delete_extra){
209 cmd += " --delete";
210 }
211
212 if (delete_after){
213 cmd += " --delete-after";
214 }
215
216 cmd += " --force"; // allow deletion of non-empty directories
217
218 //cmd += " --numeric-ids";
219
220 cmd += " --stats";
221
222 cmd += " --sparse";
223
224 //if (relative){
225 // cmd += " --relative";
226 //}
227
228 if (delete_excluded){
229 cmd += " --delete-excluded";
230 }
231
232 if (dry_run){
233 cmd += " --dry-run";
234 }
235
236 if (link_from_path.length > 0){
237 if (!link_from_path.has_suffix("/")){
238 link_from_path = "%s/".printf(link_from_path);
239 }
240
241 cmd += " --link-dest='%s'".printf(escape_single_quote(link_from_path));
242 }
243
244 if (rsync_log_file.length > 0){
245 cmd += " --log-file='%s'".printf(escape_single_quote(rsync_log_file));
246 }
247
248 if (exclude_from_file.length > 0){
249 cmd += " --exclude-from='%s'".printf(escape_single_quote(exclude_from_file));
250
252 cmd += " --delete-excluded";
253 }
254 }
255
257
259
260 cmd += " '%s/'".printf(escape_single_quote(source_path));
261
262 cmd += " '%s/'".printf(escape_single_quote(dest_path));
263
264 return cmd;
265 }
266
267 public Gee.ArrayList<FileItem> parse_log(string log_file_path){
268
269 var list = new Gee.ArrayList<FileItem>();
270
271 string log_file = log_file_path;
272 DataOutputStream dos_changes = null;
273
274 if (!log_file.has_suffix("-changes")){
275
276 string log_file_changes = log_file_path + "-changes";
277
278 if (file_exists(log_file_changes)){
279 // use it
280 log_file = log_file_changes;
281 }
282 else{
283 // create one by initializing dos_changes
284 try {
285 var file = File.new_for_path(log_file_changes);
286 if (file.query_exists()){
287 file.delete();
288 }
289 var file_stream = file.create (FileCreateFlags.REPLACE_DESTINATION);
290 dos_changes = new DataOutputStream (file_stream);
291 }
292 catch (Error e) {
293 log_error (e.message);
294 }
295 }
296 }
297
298 log_debug("RsyncTask: parse_log()");
299 log_debug("log_file = %s".printf(log_file));
300
301 prg_count = 0;
302 prg_count_total = file_line_count(log_file) ?? 0;
303
304 try {
305 string line;
306 var file = File.new_for_path(log_file);
307 if (!file.query_exists ()) {
308 log_error(_("File not found") + ": %s".printf(log_file));
309 return list;
310 }
311
312 var dis = new DataInputStream (file.read());
313
314 string item_path = "";
315 string item_disk_path = "";
316 string item_status = "";
317
318 string item_basepath = path_combine(file_parent(log_file_path), "localhost");
319
320 while ((line = dis.read_line(null)) != null) {
321
322 prg_count++;
323
324 if (line.strip().length == 0) { continue; }
325
326 item_path = "";
327
328 MatchInfo match;
329 if (regex_list[RegexType.Created].match(line, 0, out match)
330 || regex_list[RegexType.LogCreated].match(line, 0, out match)) {
331
332 if (dos_changes != null){
333 dos_changes.put_string("%s\n".printf(line));
334 }
335
336 //log_debug("matched: created:%s".printf(line));
337
338 item_path = match.fetch(3).split(" -> ")[0].strip();
339 /*item_type = FileType.REGULAR;
340 if (match.fetch(2) == "d"){
341 item_type = FileType.DIRECTORY;
342 }
343 else if (match.fetch(2) == "L"){
344 item_is_symlink = true;
345 }*/
346 item_status = "created";
347 }
348 else if (regex_list[RegexType.LogDeleted].match(line, 0, out match)
349 || regex_list[RegexType.Deleted].match(line, 0, out match)) {
350
351 //log_debug("matched: deleted:%s".printf(line));
352
353 if (dos_changes != null){
354 dos_changes.put_string("%s\n".printf(line));
355 }
356
357 item_path = match.fetch(1).split(" -> ")[0].strip();
358 //item_type = item_path.has_suffix("/") ? FileType.DIRECTORY : FileType.REGULAR;
359 item_status = "deleted";
360 }
361 else if (regex_list[RegexType.Modified].match(line, 0, out match)
362 || regex_list[RegexType.LogModified].match(line, 0, out match)) {
363
364 //log_debug("matched: modified:%s".printf(line));
365
366 if (dos_changes != null){
367 dos_changes.put_string("%s\n".printf(line));
368 }
369
370 item_path = match.fetch(12).split(" -> ")[0].strip();
371
372 /*if (match.fetch(2) == "d"){
373 item_type = FileType.DIRECTORY;
374 }
375 else if (match.fetch(2) == "L"){
376 item_is_symlink = true;
377 }*/
378
379 if (match.fetch(3) == "c"){
380 item_status = "checksum";
381 }
382 else if (match.fetch(4) == "s"){
383 item_status = "size";
384 }
385 else if (match.fetch(5) == "t"){
386 item_status = "timestamp";
387 }
388 else if (match.fetch(6) == "p"){
389 item_status = "permissions";
390 }
391 else if (match.fetch(7) == "o"){
392 item_status = "owner";
393 }
394 else if (match.fetch(8) == "g"){
395 item_status = "group";
396 }
397 }
398 else if (regex_list[RegexType.LogUnchanged].match(line, 0, out match)) {
399 // ignore
400 }
401 else{
402 log_debug("not-matched: %s".printf(line));
403 }
404
405 if ((item_path.length > 0) && (item_path != "/./") && (item_path != "./")){
406
407 item_disk_path = path_combine(item_basepath, item_path);
408
409 var item = new FileItem.from_disk_path_with_basic_info(item_disk_path);
410 item.file_status = item_status;
411 list.add(item);
412 //log_debug("added: %s".printf(item_path));
413 }
414 }
415 }
416 catch (Error e) {
417 log_error (e.message);
418 }
419
420 if (dos_changes != null){
421 // archive the raw log file
422 log_msg("Archiving: %s".printf(log_file_path));
423 file_gzip(log_file_path);
424 }
425
426 log_debug("RsyncTask: parse_log(): exit");
427
428 return list;
429 }
430
431 // execution ----------------------------
432
436
440
441 public bool update_progress_parse_console_output (string line) {
442 if ((line == null) || (line.length == 0)) {
443 return true;
444 }
445
447
448 if (prg_count_total > 0){
451 }
452
453 //MatchInfo match;
454 //if (regex_list[RegexType.Status].match(line, 0, out match)) {
455 // status_line = match.fetch(12);
456
457 //status_lines.push_tail(status_line);
458 //if (status_lines.get_length() > 15){
459 // status_lines.pop_head();
460 //}
461 //}
462 MatchInfo match;
463 if (regex_list[RegexType.Created].match(line, 0, out match)) {
464
465 //log_debug("matched: created:%s".printf(line));
466
468 status_line = match.fetch(3).split(" -> ")[0].strip();
469 log.append(line + "\n");
470 }
471 else if (regex_list[RegexType.Deleted].match(line, 0, out match)) {
472
473 //log_debug("matched: deleted:%s".printf(line));
474
476 status_line = match.fetch(1).split(" -> ")[0].strip();
477 log.append(line + "\n");
478 }
479 else if (regex_list[RegexType.Unchanged].match(line, 0, out match)) {
480
481 //log_debug("matched: unchanged:%s".printf(line));
482
484 status_line = match.fetch(3).split(" -> ")[0].strip();
485 }
486 else if (regex_list[RegexType.Modified].match(line, 0, out match)) {
487
488 //log_debug("matched: modified:%s".printf(line));
489
491 status_line = match.fetch(12).split(" -> ")[0].strip();
492 log.append(line + "\n");
493
494 if (match.fetch(3) == "c"){
496 }
497 else if (match.fetch(4) == "s"){
498 count_size++;
499 }
500 else if (match.fetch(5) == "t"){
502 }
503 else if (match.fetch(6) == "p"){
505 }
506 else if (match.fetch(7) == "o"){
507 count_owner++;
508 }
509 else if (match.fetch(8) == "g"){
510 count_group++;
511 }
512 else{
514 }
515 }
516 else if (regex_list[RegexType.TotalSize].match(line, 0, out match)) {
517 total_size = int64.parse(match.fetch(1).replace(",",""));
518 }
519 else{
520 //log_debug("not-matched: %s".printf(line));
521 }
522
523 return true;
524 }
525
526 protected override void finish_task() {
527 if (0 < rsync_log_file.length) {
528 file_write(rsync_log_file + "-changes", log.str);
529 }
530 }
531
532}
#define _(s)
int64 prg_count
string err_line
int64 prg_count_total
double progress
string status_line
string out_line
FileItem. from_disk_path_with_basic_info(string _file_path)
Definition FileItem.vala:67
string dest_path
void init_regular_expressions()
override void prepare()
int64 count_group
int64 count_deleted
bool dry_run
int64 count_owner
Gee.ArrayList< FileItem > parse_log(string log_file_path)
int64 count_modified
bool delete_excluded
override void parse_stderr_line(string err_line)
string rsync_log_file
override void finish_task()
string source_path
Regex[] regex_list
override string build_script()
string link_from_path
int64 count_permissions
int64 count_size
int64 count_created
string exclude_from_file
bool verbose
int64 count_timestamp
StringBuilder log
GLib.Queue< string > status_lines
int64 count_unchanged
int64 status_line_count
int64 count_checksum
bool delete_extra
bool update_progress_parse_console_output(string line)
bool delete_after
bool relative
bool io_nice
int64 total_size
override void parse_stdout_line(string out_line)
bool file_exists(string file_path)
string escape_single_quote(string file_path)
string remove_trailing_slash(string path)
string file_parent(string file_path)
bool file_write(string file_path, string contents)
string path_combine(string path1, string path2)
int64? file_line_count(string file_path)
bool file_gzip(string src_file)
void log_debug(string message)
void log_error(string message, bool highlight=false, bool is_warning=false)
void log_msg(string message, bool highlight=false)