/* vifm
 * Copyright (C) 2001 Ken Steen.
 * Copyright (C) 2011 xaizek.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#ifndef VIFM__BACKGROUND_H__
#define VIFM__BACKGROUND_H__

#ifdef _WIN32
#include <windef.h>
#endif

#include <sys/types.h> /* pid_t */

#include <stdio.h>

#include "compat/pthread.h"
#include "utils/utils.h"

/* Special value of total amount of work in bg_job_t structure to indicate
 * undefined total number of countable operations. */
#define BG_UNDEFINED_TOTAL (-1)

struct bg_job_t;

/* Type of a function to invoke when the job is done. */
typedef void (*bg_job_exit_func)(struct bg_job_t *job, void *data);

/* Type of background job. */
typedef enum
{
	BJT_COMMAND,   /* Tracked external command started by Vifm. */
	BJT_OPERATION, /* Important internal background operation, e.g. copying. */
	BJT_TASK,      /* Unimportant internal background operations, e.g. calculation
	                  of directory size. */
}
BgJobType;

/* Job flags passed to bg_run_external_job(). */
typedef enum
{
	BJF_NONE            = 0,      /* No flags set. */
	BJF_JOB_BAR_VISIBLE = 1 << 0, /* Makes the job appear on the job bar. */
	BJF_MENU_VISIBLE    = 1 << 1, /* Makes the job appear in :jobs menu. */
	BJF_SUPPLY_INPUT    = 1 << 2, /* Open a pipe for standard input stream. */
	BJF_CAPTURE_OUT     = 1 << 3, /* Capture output stream(s). */
	BJF_MERGE_STREAMS   = 1 << 4, /* Merge error stream into output stream. */
	BJF_KEEP_IN_FG      = 1 << 5, /* Do not detach from terminal session or
	                                 process group. */
}
BgJobFlags;

/* Auxiliary structure to be updated by background tasks while they progress. */
typedef struct bg_op_t
{
	int total; /* Total number of coarse operations. */
	int done;  /* Number of already processed coarse operations. */

	int progress; /* Progress in percents.  -1 if task doesn't provide one. */
	char *descr;  /* Description of current activity, can be NULL. */

	int cancelled; /* Whether cancellation has been requested. */
}
bg_op_t;

/* Description of background activity. */
typedef struct bg_job_t
{
	BgJobType type; /* Type of background job. */
	int cancelled;  /* Whether cancellation has been requested. */
	pid_t pid;
	char *cmd;

	int skip_errors;   /* Do not show future errors. */

	/* The lock is meant to guard error-related fields. */
	pthread_spinlock_t errors_lock;
	char *new_errors;      /* Portion of stderr which hasn't been shown yet. */
	size_t new_errors_len; /* Length of the new_errors field. */
	char *errors;          /* Whole error stream collected. */
	size_t errors_len;     /* Length of the errors field. */

	/* The lock is meant to guard state-related fields. */
	pthread_spinlock_t status_lock;
	int running;   /* Whether this job is still running. */
	int erroring;  /* Whether error thread still handles this job. */
	int use_count; /* Count of uses of this job entry. */
	int exit_code; /* Exit code of external command. */

	FILE *input;  /* File stream of standard input or NULL. */
	FILE *output; /* File stream of standard output or NULL. */

	bg_job_exit_func exit_cb; /* Function to invoke when the job exits. */
	void *exit_cb_arg;        /* Argument to pass to that function. */

	int with_bg_op;                /* Whether bg_op* fields are active. */
	int on_job_bar;                /* Whether this task was put on a job bar. */
	pthread_spinlock_t bg_op_lock; /* Lock for accessing bg_op field. */
	bg_op_t bg_op;                 /* Progress and cancellation information. */

#ifndef _WIN32
	int err_stream;    /* stderr stream of the job or -1. */
#else
	HANDLE err_stream; /* stderr stream of the job or invalid handle. */
	HANDLE hprocess;   /* Handle to the process of the job or invalid handle. */
	HANDLE hjob;       /* Handle to the process job object. */
#endif

	struct bg_job_t *next;     /* Link to the next element in bg_jobs list. */

	/* Used by error thread for BJT_COMMAND jobs. */
	struct bg_job_t *err_next; /* Link to the next element in error read list. */
	int drained;               /* Whether error stream of no interest anymore. */

	int in_menu; /* Whether this task is visible in :jobs menu. */
}
bg_job_t;

/* Background task entry point function signature. */
typedef void (*bg_task_func)(bg_op_t *bg_op, void *arg);

/* List of background jobs. */
extern bg_job_t *bg_jobs;

/* Prepare background unit for the work.  Returns zero on success. */
int bg_init(void);

/* Creates background job running external command.  Returns zero on success,
 * otherwise non-zero is returned.  If *input is not NULL, it's set to input
 * pipe of the background process.  The caller becomes responsible for the
 * stream. */
int bg_run_external(const char cmd[], int keep_in_fg, int skip_errors,
		ShellRequester by, FILE **input);

/* Creates background job running external command which does not interact with
 * the user and is detached from controlling terminal.  Upon creation the job
 * has one extra use, which needs to be decremented for it to be freed.  The
 * optional description is for the job bar and can be NULL.  descr and pwd can
 * be NULL, non-NULL pwd should point to a directory that can be opened.
 * Returns the job or NULL on error. */
bg_job_t * bg_run_external_job(const char cmd[], BgJobFlags flags,
		const char descr[], const char pwd[]);

struct cancellation_t;

/* Runs command in background and displays its errors to a user.  To determine
 * an error uses both stderr stream and exit status.  Returns zero on success,
 * otherwise non-zero is returned. */
int bg_and_wait_for_errors(char cmd[],
		const struct cancellation_t *cancellation);

/* Runs command in a background and redirects its streams to specified streams
 * that are non-NULL, otherwise they are left connected to the terminal.  Don't
 * pass pipe for input, it can cause deadlock.  Returns id of background
 * process ((pid_t)0 for non-*nix like systems) or (pid_t)-1 on error. */
pid_t bg_run_and_capture(char cmd[], int user_sh, FILE *in, FILE **out,
		FILE **err);

/* Checks status of background jobs (their streams and state).  Removes finished
 * ones from the list, optionally displays any pending error messages, corrects
 * job bar if needed. */
void bg_check(int show_errors);

/* Starts new background task, which is run in a separate thread.  Returns zero
 * on success, otherwise non-zero is returned. */
int bg_execute(const char descr[], const char op_descr[], int total,
		int important, bg_task_func task_func, void *args);

/* Checks whether there are any internal jobs (important_only is non-zero) or
 * jobs or tasks (important_only is zero) running in background.  External
 * applications whose state is tracked are always ignored by this function. */
int bg_has_active_jobs(int important_only);

/* Sets exit callback for the job. */
void bg_job_set_exit_cb(bg_job_t *job, bg_job_exit_func cb, void *arg);

/* Cancels the job.  Returns non-zero if job wasn't cancelled before, but is
 * after this call, otherwise zero is returned. */
int bg_job_cancel(bg_job_t *job);

/* Checks whether the job has been cancelled.  Returns non-zero if so, otherwise
 * zero is returned. */
int bg_job_cancelled(bg_job_t *job);

/* Terminates the job in a forceful way leaving it no chance to respond. */
void bg_job_terminate(bg_job_t *job);

/* Checks whether the job is still running.  Returns non-zero if so, otherwise
 * zero is returned. */
int bg_job_is_running(bg_job_t *job);

/* Checks whether the job was killed.  Returns non-zero if so, otherwise zero is
 * returned. */
int bg_job_was_killed(bg_job_t *job);

/* Waits for external command to finish (don't pass any other kind of job).
 * Returns zero on success, otherwise non-zero is returned. */
int bg_job_wait(bg_job_t *job);

/* Waits until error thread is done with this job.  The job might have nothing
 * to do with the error thread and might actually be running with errors still
 * comming, but it's possible that the job has exited and not all errors were
 * processed which is the interesting case.  Returns zero on success or non-zero
 * on failure to wait for error thread to release the job (timeout). */
int bg_job_wait_errors(bg_job_t *job);

/* Increases use counter of the job.  Doing this prevents object deletion while
 * it's still in use. */
void bg_job_incref(bg_job_t *job);

/* Decreases use counter of the job.  Assume the object is freed after calling
 * this functions (unless you've incremented the counter more than once). */
void bg_job_decref(bg_job_t *job);

/* Temporary locks bg_op_t structure to ensure that it's not modified by
 * anyone during reading/updating its fields.  The structure must be part of
 * bg_job_t.  Returns non-zero on success. */
int bg_op_lock(bg_op_t *bg_op);

/* Unlocks bg_op_t structure.  The structure must be part of bg_job_t. */
void bg_op_unlock(bg_op_t *bg_op);

/* Callback-like function to report that state of background operation
 * changed. */
void bg_op_changed(bg_op_t *bg_op);

/* Convenience method to update description of background job, use
 * lock -> <change> -> unlock -> changed sequence for more generic cases.  Fires
 * operation change. */
void bg_op_set_descr(bg_op_t *bg_op, const char descr[]);

/* Convenience method to check for background job cancellation, use
 * lock -> <check> -> unlock -> changed sequence for more generic cases.
 * Returns non-zero if cancellation requested, otherwise zero is returned. */
int bg_op_cancelled(bg_op_t *bg_op);

#endif /* VIFM__BACKGROUND_H__ */

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
