Skip to content
API Documentation
BastionGuard™
AsyncTask.vala
Go to the documentation of this file.
1/*
2 * AsyncTask.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 abstract class AsyncTask : GLib.Object{
32
33 private string err_line = "";
34 private string out_line = "";
35 private DataOutputStream dos_in = null;
36 private DataInputStream dis_out = null;
37 private DataInputStream dis_err = null;
38 protected DataOutputStream dos_log = null;
39
40 private bool stdout_is_open = false;
41 private bool stderr_is_open = false;
42
43 protected Pid child_pid = 0;
44 private int input_fd = -1;
45 private int output_fd = -1;
46 private int error_fd = -1;
47 private bool finish_called = false;
48
49 protected string script_file = "";
50 protected string working_dir = "";
51
52 // public
53 public AppStatus status = AppStatus.NOT_STARTED;
54
55 private string _status_line = "";
56 public GLib.Mutex status_line_mutex;
57
58 public int exit_code = 0;
59 public GLib.Timer timer;
60 private double timerOffset = 0.0; // milliseconds to be added to the current timer - this is to compensate for pauses (timer restarts)
61 public double progress = 0.0;
62 public double percent = 0.0;
63 public int64 prg_count = 0;
64 public int64 prg_count_total = 0;
65
66 // signals
67 public signal void stdout_line_read(string line);
68 public signal void stderr_line_read(string line);
69 public signal void task_complete();
70
71 [CCode(notify = false)]
72 public string status_line
73 {
74 owned get {
75 return _get_status_line();
76 }
77
78 set
79 {
80 status_line_mutex.lock();
81 _status_line = value;
82 status_line_mutex.unlock();
83 }
84 }
85
86 private string _get_status_line() {
87 string ret = "";
88
89 if (status_line_mutex.trylock()) {
90 ret = _status_line;
91 status_line_mutex.unlock();
92 }
93
94 return ret;
95 }
96
97 protected AsyncTask(){
99 script_file = path_combine(working_dir, "script.sh");
100
101 status_line_mutex = GLib.Mutex();
102
104 }
105
106 public virtual void prepare() {
107 string script_text = build_script();
108 log_debug(script_text);
110
111 log_debug("AsyncTask:prepare(): saved: %s".printf(script_file));
112 }
113
114 protected abstract string build_script();
115
116 protected virtual bool begin() {
117 status = AppStatus.RUNNING;
118
119 bool has_started = true;
120 finish_called = false;
121
122 prg_count = 0;
123
124 string[] spawn_args = new string[1];
125 spawn_args[0] = script_file;
126
127 string[] spawn_env = Environ.get();
128
129 try {
130 // start timer
131 timer = new GLib.Timer();
132 timer.start();
133
134 // execute script file
135 Process.spawn_async_with_pipes(
136 working_dir, // working dir
137 spawn_args, // argv
138 spawn_env, // environment
139 SpawnFlags.SEARCH_PATH,
140 null, // child_setup
141 out child_pid,
142 out input_fd,
143 out output_fd,
144 out error_fd);
145
146 log_debug("AsyncTask: child_pid: %d".printf(child_pid));
147
148 // create stream readers
149 UnixOutputStream uos_in = new UnixOutputStream(input_fd, true);
150 UnixInputStream uis_out = new UnixInputStream(output_fd, true);
151 UnixInputStream uis_err = new UnixInputStream(error_fd, true);
152 dos_in = new DataOutputStream(uos_in);
153 dis_out = new DataInputStream(uis_out);
154 dis_err = new DataInputStream(uis_err);
155 dis_out.newline_type = DataStreamNewlineType.ANY;
156 dis_err.newline_type = DataStreamNewlineType.ANY;
157
158 try {
159 //start thread for reading output stream
160 new Thread<void>.try ("async-task-stdout-reader", read_stdout);
161 } catch (GLib.Error e) {
162 log_error ("AsyncTask.begin():create_thread:read_stdout()");
163 log_error (e.message);
164 }
165
166 try {
167 //start thread for reading error stream
168 new Thread<void>.try ("async-task-stderr-reader", read_stderr);
169 } catch (GLib.Error e) {
170 log_error ("AsyncTask.begin():create_thread:read_stderr()");
171 log_error (e.message);
172 }
173 }
174 catch (Error e) {
175 log_error ("AsyncTask.begin()");
176 log_error(e.message);
177 has_started = false;
178 //status = AppStatus.FINISHED;
179 }
180
181 return has_started;
182 }
183
184 private void read_stdout() {
185 try {
186 stdout_is_open = true;
187
188 out_line = dis_out.read_line (null);
189 while (out_line != null) {
190 //log_msg("O: " + out_line);
191 if (out_line.length > 0) {
193 stdout_line_read(out_line); //signal
194 }
195 out_line = dis_out.read_line (null); //read next
196 }
197
198 stdout_is_open = false;
199
200 // dispose stdout
201 try {
202 if (dis_out != null) {
203 dis_out.close();
204 }
205 }
206 catch (GLib.Error ignored) {}
207 dis_out = null;
208
209 // check if complete
211 finish();
212 }
213 }
214 catch (Error e) {
215 log_error ("AsyncTask.read_stdout()");
216 log_error (e.message);
217 }
218 }
219
220 private void read_stderr() {
221 try {
222 stderr_is_open = true;
223
224 err_line = dis_err.read_line (null);
225 while (err_line != null) {
226 if (err_line.length > 0){
228 stderr_line_read(err_line); //signal
229 }
230 err_line = dis_err.read_line (null); //read next
231 }
232
233 stderr_is_open = false;
234
235 // dispose stderr
236 try {
237 if (dis_err != null) {
238 dis_err.close();
239 }
240 }
241 catch (GLib.Error ignored) {}
242 dis_err = null;
243
244 // check if complete
246 finish();
247 }
248 }
249 catch (Error e) {
250 log_error ("AsyncTask.read_stderr()");
251 log_error (e.message);
252 }
253 }
254
255 public void write_stdin(string line){
256 try{
257 if (status == AppStatus.RUNNING){
258 dos_in.put_string(line + "\n");
259 }
260 else{
261 log_error ("AsyncTask.write_stdin(): NOT RUNNING");
262 }
263 }
264 catch(Error e){
265 log_error ("AsyncTask.write_stdin(): %s".printf(line));
266 log_error (e.message);
267 }
268 }
269
270 public virtual void execute() {
271 log_debug("AsyncTask:execute()");
272 prepare();
273 begin();
274 }
275
276 protected abstract void parse_stdout_line(string out_line);
277
278 protected abstract void parse_stderr_line(string err_line);
279
280 private void finish(){
281
282 // finish() gets called by 2 threads but should be executed only once
283 if (finish_called) { return; }
284 finish_called = true;
285
286 log_debug("AsyncTask: finish(): enter");
287
288 // dispose stdin
289 try{
290 if (dos_in != null) {
291 dos_in.close();
292 }
293 }
294 catch(GLib.IOError e) {
295 // ignore
296 }
297 dos_in = null;
298
299 // dispose child process
300 Process.close_pid(child_pid); //required on Windows, doesn't do anything on Unix
301
303
304 status_line = "";
305 err_line = "";
306 out_line = "";
307
308 timer.stop();
309
310 finish_task();
311
312 if ((status != AppStatus.CANCELLED) && (status != AppStatus.PASSWORD_REQUIRED)) {
313 status = AppStatus.FINISHED;
314 }
315
316 //dir_delete(working_dir);
317
318 task_complete(); //signal
319 }
320
321 // can be overloaded by subclasses, that wish to do special stuff during finish
322 protected virtual void finish_task() {}
323
324 protected int read_exit_code(){
325
326 exit_code = -1;
327 var path = file_parent(script_file) + "/status";
328 if (file_exists(path)){
329 var txt = file_read(path);
330 exit_code = int.parse(txt);
331 }
332 log_debug("exit_code: %d".printf(exit_code));
333 return exit_code;
334 }
335
336 public bool is_running(){
337 return (status == AppStatus.RUNNING);
338 }
339
340 // public actions --------------
341
342 public void stop(AppStatus status_to_update = AppStatus.CANCELLED) {
343 status = status_to_update;
344
345 if(0 != child_pid) {
347 child_pid = 0;
348
349 log_debug("process_quit: %d ".printf(child_pid));
350 }
351 }
352
353 public void pause(AppStatus status_to_update = AppStatus.PAUSED) {
354 status = status_to_update;
355
356 if(0 != child_pid) {
357 TeeJee.ProcessHelper.process_send_signal(this.child_pid, Posix.Signal.STOP, true);
358
359 // "pause" timer
360 this.timerOffset += TeeJee.System.timer_elapsed(this.timer, true);
361
362 log_debug("process_paused: %d ".printf(this.child_pid));
363 }
364 }
365
366 // unpause (continue) the task
367 public void resume(AppStatus status_to_update = AppStatus.RUNNING) {
368 status = status_to_update;
369
370 if(0 != child_pid) {
371 TeeJee.ProcessHelper.process_send_signal(this.child_pid, Posix.Signal.CONT, true);
372
373 // restart timer
374 this.timer = new GLib.Timer();
375 this.timer.start();
376
377 log_debug("process_resumed: %d ".printf(this.child_pid));
378 }
379 }
380
381 private double elapsed {
382 get {
383 double elapsed = timerOffset;
384 if(this.status != AppStatus.PAUSED) {
385 elapsed += TeeJee.System.timer_elapsed(timer);
386 }
387 return elapsed;
388 }
389 }
390
391 public string stat_time_elapsed{
392 owned get{
393 return TeeJee.Misc.format_duration(this.elapsed);
394 }
395 }
396
397 public string stat_time_remaining{
398 owned get{
399 if (this.progress > 0){
400 double remaining = ((this.elapsed / this.progress) * (1.0 - this.progress));
401 if (remaining < 0){
402 remaining = 0;
403 }
404 return TeeJee.Misc.format_duration(remaining);
405 }
406 else{
407 return "???";
408 }
409 }
410 }
411
412 public void print_app_status(){
413
414 switch(status){
415 case AppStatus.NOT_STARTED:
416 log_debug("status=%s".printf("NOT_STARTED"));
417 break;
418 case AppStatus.RUNNING:
419 log_debug("status=%s".printf("RUNNING"));
420 break;
421 case AppStatus.PAUSED:
422 log_debug("status=%s".printf("PAUSED"));
423 break;
424 case AppStatus.FINISHED:
425 log_debug("status=%s".printf("FINISHED"));
426 break;
427 case AppStatus.CANCELLED:
428 log_debug("status=%s".printf("CANCELLED"));
429 break;
430 case AppStatus.PASSWORD_REQUIRED:
431 log_debug("status=%s".printf("PASSWORD_REQUIRED"));
432 break;
433 }
434 }
435}
436
445
446/* Sample Subclass:
447public class RsyncTask : AsyncTask{
448
449 public bool delete_extra = true;
450 public string rsync_log_file = "";
451 public string exclude_from_file = "";
452 public string source_path = "";
453 public string dest_path = "";
454 public bool verbose = true;
455
456 public RsyncTask(string _script_file, string _working_dir, string _log_file){
457 working_dir = _working_dir;
458 script_file = _script_file;
459 log_file = _log_file;
460 }
461
462 protected override string build_script() {
463 var cmd = "rsync -ai";
464
465 if (verbose){
466 cmd += " --verbose";
467 }
468 else{
469 cmd += " --quiet";
470 }
471
472 if (delete_extra){
473 cmd += " --delete";
474 }
475
476 cmd += " --numeric-ids --stats --relative --delete-excluded";
477
478 if (rsync_log_file.length > 0){
479 cmd += " --log-file='%s'".printf(escape_single_quote(rsync_log_file));
480 }
481
482 if (exclude_from_file.length > 0){
483 cmd += " --exclude-from='%s'".printf(escape_single_quote(exclude_from_file));
484 }
485
486 source_path = remove_trailing_slash(source_path);
487
488 dest_path = remove_trailing_slash(dest_path);
489
490 cmd += " '%s/'".printf(escape_single_quote(source_path));
491
492 cmd += " '%s/'".printf(escape_single_quote(dest_path));
493
494 //cmd += " /. \"%s\"".printf(sync_path + "/localhost/");
495
496 return cmd;
497 }
498
499 // execution ----------------------------
500
501 public override void parse_stdout_line(string out_line){
502 update_progress_parse_console_output(out_line);
503 }
504
505 public override void parse_stderr_line(string err_line){
506 update_progress_parse_console_output(err_line);
507 }
508
509 public bool update_progress_parse_console_output (string line) {
510 if ((line == null) || (line.length == 0)) {
511 return true;
512 }
513
514 return true;
515 }
516}
517*/
AppStatus
@ PASSWORD_REQUIRED
void print_app_status()
int output_fd
double percent
string??? stat_time_remaining
virtual void prepare()
void parse_stderr_line(string err_line)
void pause(AppStatus status_to_update=AppStatus.PAUSED)
double elapsed
GLib.Mutex status_line_mutex
void read_stderr()
void read_stdout()
int64 prg_count
bool stdout_is_open
signal void stdout_line_read(string line)
GLib.Timer timer
Pid child_pid
DataOutputStream dos_in
string stat_time_elapsed
void stop(AppStatus status_to_update=AppStatus.CANCELLED)
string script_file
virtual void execute()
string build_script()
string err_line
bool stderr_is_open
void resume(AppStatus status_to_update=AppStatus.RUNNING)
signal void stderr_line_read(string line)
int read_exit_code()
int64 prg_count_total
virtual void finish_task()
void finish()
DataOutputStream dos_log
string working_dir
AppStatus status
void write_stdin(string line)
bool is_running()
double progress
int exit_code
DataInputStream dis_out
string status_line
double timerOffset
virtual bool begin()
bool finish_called
signal void task_complete()
string _get_status_line()
void parse_stdout_line(string out_line)
DataInputStream dis_err
string out_line
string _status_line
bool file_exists(string file_path)
string? file_read(string file_path)
string file_parent(string file_path)
string path_combine(string path1, string path2)
bool dir_create(string dir_path, bool show_message=false)
void log_debug(string message)
void log_error(string message, bool highlight=false, bool is_warning=false)
string timestamp_for_path()
void process_quit(Pid process_pid, bool killChildren=true)
string? save_bash_script_temp(string commands, string? script_path=null, bool force_locale=true, bool supress_errors=false)