Run some actions in a separate thread

Some actions need to be threaded so we will run those in a
separate thread and deny requests to thread more actions if new
requests come in while a thread is already running.

Change-Id: I966c538e67860a6d8fe556e5a2eb7f7d1a987e74
Signed-off-by: Vojtech Bocek <vbocek@gmail.com>
diff --git a/gui/action.cpp b/gui/action.cpp
index ef325aa..d54ea0a 100644
--- a/gui/action.cpp
+++ b/gui/action.cpp
@@ -66,11 +66,70 @@
 void curtainClose(void);
 
 GUIAction::mapFunc GUIAction::mf;
+std::set<string> GUIAction::setActionsRunningInCallerThread;
 static string zip_queue[10];
 static int zip_queue_index;
 static pthread_t terminal_command;
 pid_t sideload_child_pid;
 
+static ActionThread action_thread;
+
+static void *ActionThread_work_wrapper(void *data)
+{
+	action_thread.run(data);
+	return NULL;
+}
+
+ActionThread::ActionThread()
+{
+	m_thread_running = false;
+	pthread_mutex_init(&m_act_lock, NULL);
+}
+
+ActionThread::~ActionThread()
+{
+	pthread_mutex_lock(&m_act_lock);
+	if(m_thread_running) {
+		pthread_mutex_unlock(&m_act_lock);
+		pthread_join(m_thread, NULL);
+	} else {
+		pthread_mutex_unlock(&m_act_lock);
+	}
+	pthread_mutex_destroy(&m_act_lock);
+}
+
+void ActionThread::threadActions(GUIAction *act, size_t start_index)
+{
+	pthread_mutex_lock(&m_act_lock);
+	if (m_thread_running) {
+		pthread_mutex_unlock(&m_act_lock);
+		LOGERR("Another threaded action is already running -- not running actions '%s' and following\n", act->mActions[start_index].mFunction.c_str());
+	} else {
+		m_thread_running = true;
+		pthread_mutex_unlock(&m_act_lock);
+		ThreadData *d = new ThreadData;
+		d->act = act;
+		d->start_index = start_index;
+
+		pthread_create(&m_thread, NULL, &ActionThread_work_wrapper, d);
+	}
+}
+
+void ActionThread::run(void *data)
+{
+	ThreadData *d = (ThreadData*)data;
+	GUIAction* act = d->act;
+
+	std::vector<GUIAction::Action>::iterator it;
+	for (it = act->mActions.begin() + d->start_index; it != act->mActions.end(); ++it)
+		act->doAction(*it);
+
+	pthread_mutex_lock(&m_act_lock);
+	m_thread_running = false;
+	pthread_mutex_unlock(&m_act_lock);
+	delete d;
+}
+
 GUIAction::GUIAction(xml_node<>* node)
 	: GUIObject(node)
 {
@@ -81,6 +140,7 @@
 	if (!node)  return;
 
 	if (mf.empty()) {
+		// These actions will be run in the caller's thread
 		mf["reboot"] = &GUIAction::reboot;
 		mf["home"] = &GUIAction::home;
 		mf["key"] = &GUIAction::key;
@@ -108,9 +168,19 @@
 		mf["getpartitiondetails"] = &GUIAction::getpartitiondetails;
 		mf["screenshot"] = &GUIAction::screenshot;
 		mf["setbrightness"] = &GUIAction::setbrightness;
-
-		// threaded actions
 		mf["fileexists"] = &GUIAction::fileexists;
+		mf["killterminal"] = &GUIAction::killterminal;
+		mf["checkbackupname"] = &GUIAction::checkbackupname;
+		mf["adbsideloadcancel"] = &GUIAction::adbsideloadcancel;
+		mf["fixsu"] = &GUIAction::fixsu;
+		mf["startmtp"] = &GUIAction::startmtp;
+		mf["stopmtp"] = &GUIAction::stopmtp;
+
+		// remember actions that run in the caller thread
+		for (mapFunc::const_iterator it = mf.begin(); it != mf.end(); ++it)
+			setActionsRunningInCallerThread.insert(it->first);
+
+		// These actions will run in a separate thread
 		mf["flash"] = &GUIAction::flash;
 		mf["wipe"] = &GUIAction::wipe;
 		mf["refreshsizes"] = &GUIAction::refreshsizes;
@@ -123,20 +193,14 @@
 		mf["htcdumlockreflashrecovery"] = &GUIAction::htcdumlockreflashrecovery;
 		mf["cmd"] = &GUIAction::cmd;
 		mf["terminalcommand"] = &GUIAction::terminalcommand;
-		mf["killterminal"] = &GUIAction::killterminal;
 		mf["reinjecttwrp"] = &GUIAction::reinjecttwrp;
-		mf["checkbackupname"] = &GUIAction::checkbackupname;
 		mf["decrypt"] = &GUIAction::decrypt;
 		mf["adbsideload"] = &GUIAction::adbsideload;
-		mf["adbsideloadcancel"] = &GUIAction::adbsideloadcancel;
 		mf["openrecoveryscript"] = &GUIAction::openrecoveryscript;
 		mf["installsu"] = &GUIAction::installsu;
-		mf["fixsu"] = &GUIAction::fixsu;
 		mf["decrypt_backup"] = &GUIAction::decrypt_backup;
 		mf["repair"] = &GUIAction::repair;
 		mf["changefilesystem"] = &GUIAction::changefilesystem;
-		mf["startmtp"] = &GUIAction::startmtp;
-		mf["stopmtp"] = &GUIAction::stopmtp;
 	}
 
 	// First, get the action
@@ -306,6 +370,11 @@
 	return ret_val;
 }
 
+bool GUIAction::needsToRunInSeparateThread(const GUIAction::Action& action)
+{
+	return setActionsRunningInCallerThread.find(action.mFunction) == setActionsRunningInCallerThread.end();
+}
+
 int GUIAction::doActions()
 {
 	if (mActions.size() < 1)
@@ -313,7 +382,17 @@
 
 	std::vector<Action>::iterator it;
 	for (it = mActions.begin(); it != mActions.end(); ++it)
+	{
+		if (needsToRunInSeparateThread(*it))
+		{
+			// run all remaining actions in a separate thread
+			action_thread.threadActions(this, it - mActions.begin());
+			// ...and we're done here
+			break;
+		}
+
 		doAction(*it);
+	}
 
 	return 0;
 }
@@ -1168,16 +1247,52 @@
 	} else {
 		command = "cd \"" + cmdpath + "\" && " + arg + " 2>&1";;
 		LOGINFO("Actual command is: '%s'\n", command.c_str());
-		DataManager::SetValue("tw_terminal_command_thread", command);
 		DataManager::SetValue("tw_terminal_state", 1);
 		DataManager::SetValue("tw_background_thread_running", 1);
-		op_status = pthread_create(&terminal_command, NULL, command_thread, NULL);
-		if (op_status != 0) {
-			LOGERR("Error starting terminal command thread, %i.\n", op_status);
-			DataManager::SetValue("tw_terminal_state", 0);
-			DataManager::SetValue("tw_background_thread_running", 0);
-			operation_end(1);
+		FILE* fp;
+		char line[512];
+
+		fp = popen(command.c_str(), "r");
+		if (fp == NULL) {
+			LOGERR("Error opening command to run.\n");
+		} else {
+			int fd = fileno(fp), has_data = 0, check = 0, keep_going = -1, bytes_read = 0;
+			struct timeval timeout;
+			fd_set fdset;
+
+			while(keep_going)
+			{
+				FD_ZERO(&fdset);
+				FD_SET(fd, &fdset);
+				timeout.tv_sec = 0;
+				timeout.tv_usec = 400000;
+				has_data = select(fd+1, &fdset, NULL, NULL, &timeout);
+				if (has_data == 0) {
+					// Timeout reached
+					DataManager::GetValue("tw_terminal_state", check);
+					if (check == 0) {
+						keep_going = 0;
+					}
+				} else if (has_data < 0) {
+					// End of execution
+					keep_going = 0;
+				} else {
+					// Try to read output
+					memset(line, 0, sizeof(line));
+					bytes_read = read(fd, line, sizeof(line));
+					if (bytes_read > 0)
+						gui_print("%s", line); // Display output
+					else
+						keep_going = 0; // Done executing
+				}
+			}
+			fclose(fp);
 		}
+		DataManager::SetValue("tw_operation_status", 0);
+		DataManager::SetValue("tw_operation_state", 1);
+		DataManager::SetValue("tw_terminal_state", 0);
+		DataManager::SetValue("tw_background_thread_running", 0);
+		DataManager::SetValue(TW_ACTION_BUSY, 0);
 	}
 	return 0;
 }
@@ -1290,51 +1405,6 @@
 	return 0;
 }
 
-void* GUIAction::sideload_thread_fn(void *cookie)
-{
-	gui_print("Starting ADB sideload feature...\n");
-	bool mtp_was_enabled = TWFunc::Toggle_MTP(false);
-	GUIAction* this_ = (GUIAction*) cookie;
-
-	// wait for the adb connection
-	int ret = apply_from_adb("/", &sideload_child_pid);
-	DataManager::SetValue("tw_has_cancel", 0); // Remove cancel button from gui now that the zip install is going to start
-
-	if (ret != 0) {
-		if (ret == -2)
-			gui_print("You need adb 1.0.32 or newer to sideload to this device.\n");
-		ret = 1; // failure
-	} else {
-		int wipe_cache = 0;
-		int wipe_dalvik = 0;
-		DataManager::GetValue("tw_wipe_dalvik", wipe_dalvik);
-
-		if (TWinstall_zip(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache) == 0) {
-			if (wipe_cache || DataManager::GetIntValue("tw_wipe_cache"))
-				PartitionManager.Wipe_By_Path("/cache");
-			if (wipe_dalvik)
-				PartitionManager.Wipe_Dalvik_Cache();
-		} else {
-			ret = 1; // failure
-		}
-	}
-	if (sideload_child_pid) {
-		LOGINFO("Signaling child sideload process to exit.\n");
-		struct stat st;
-		// Calling stat() on this magic filename signals the minadbd
-		// subprocess to shut down.
-		stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
-		int status;
-		LOGINFO("Waiting for child sideload process to exit.\n");
-		waitpid(sideload_child_pid, &status, 0);
-	}
-
-	TWFunc::Toggle_MTP(mtp_was_enabled);
-	this_->reinject_after_flash();
-	this_->operation_end(ret);
-	return NULL;
-}
-
 int GUIAction::adbsideload(std::string arg)
 {
 	operation_start("Sideload");
@@ -1342,13 +1412,45 @@
 		simulate_progress_bar();
 		operation_end(0);
 	} else {
-		// we need to start a thread to allow the operation to be cancelable
-		pthread_t sideload_thread;
-		int rc = pthread_create(&sideload_thread, NULL, sideload_thread_fn, this);
-		if (rc != 0) {
-			LOGERR("Error starting sideload thread, rc=%i.\n", rc);
-			operation_end(1);
+		gui_print("Starting ADB sideload feature...\n");
+		bool mtp_was_enabled = TWFunc::Toggle_MTP(false);
+
+		// wait for the adb connection
+		int ret = apply_from_adb("/", &sideload_child_pid);
+		DataManager::SetValue("tw_has_cancel", 0); // Remove cancel button from gui now that the zip install is going to start
+
+		if (ret != 0) {
+			if (ret == -2)
+				gui_print("You need adb 1.0.32 or newer to sideload to this device.\n");
+			ret = 1; // failure
+		} else {
+			int wipe_cache = 0;
+			int wipe_dalvik = 0;
+			DataManager::GetValue("tw_wipe_dalvik", wipe_dalvik);
+
+			if (TWinstall_zip(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache) == 0) {
+				if (wipe_cache || DataManager::GetIntValue("tw_wipe_cache"))
+					PartitionManager.Wipe_By_Path("/cache");
+				if (wipe_dalvik)
+					PartitionManager.Wipe_Dalvik_Cache();
+			} else {
+				ret = 1; // failure
+			}
 		}
+		if (sideload_child_pid) {
+			LOGINFO("Signaling child sideload process to exit.\n");
+			struct stat st;
+			// Calling stat() on this magic filename signals the minadbd
+			// subprocess to shut down.
+			stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
+			int status;
+			LOGINFO("Waiting for child sideload process to exit.\n");
+			waitpid(sideload_child_pid, &status, 0);
+		}
+
+		TWFunc::Toggle_MTP(mtp_was_enabled);
+		reinject_after_flash();
+		operation_end(ret);
 	}
 	return 0;
 }
@@ -1378,37 +1480,6 @@
 	return 0;
 }
 
-void* GUIAction::openrecoveryscript_thread_fn(void *cookie)
-{
-	GUIAction* this_ = (GUIAction*) cookie;
-
-	// Check for the SCRIPT_FILE_TMP first as these are AOSP recovery commands
-	// that we converted to ORS commands during boot in recovery.cpp.
-	// Run those first.
-	int reboot = 0;
-	if (TWFunc::Path_Exists(SCRIPT_FILE_TMP)) {
-		gui_print("Processing AOSP recovery commands...\n");
-		if (OpenRecoveryScript::run_script_file() == 0) {
-			reboot = 1;
-		}
-	}
-	// Check for the ORS file in /cache and attempt to run those commands.
-	if (OpenRecoveryScript::check_for_script_file()) {
-		gui_print("Processing OpenRecoveryScript file...\n");
-		if (OpenRecoveryScript::run_script_file() == 0) {
-			reboot = 1;
-		}
-	}
-	if (reboot) {
-		usleep(2000000); // Sleep for 2 seconds before rebooting
-		TWFunc::tw_reboot(rb_system);
-	} else {
-		DataManager::SetValue("tw_page_done", 1);
-	}
-	this_->operation_end(1);
-	return NULL;
-}
-
 int GUIAction::openrecoveryscript(std::string arg)
 {
 	operation_start("OpenRecoveryScript");
@@ -1416,13 +1487,31 @@
 		simulate_progress_bar();
 		operation_end(0);
 	} else {
-		// we need to start a thread to allow the action page to display properly
-		pthread_t openrecoveryscript_thread;
-		int rc = pthread_create(&openrecoveryscript_thread, NULL, openrecoveryscript_thread_fn, this);
-		if (rc != 0) {
-			LOGERR("Error starting sideload thread, rc=%i.\n", rc);
-			operation_end(1);
+		// Check for the SCRIPT_FILE_TMP first as these are AOSP recovery commands
+		// that we converted to ORS commands during boot in recovery.cpp.
+		// Run those first.
+		int reboot = 0;
+		if (TWFunc::Path_Exists(SCRIPT_FILE_TMP)) {
+			gui_print("Processing AOSP recovery commands...\n");
+			if (OpenRecoveryScript::run_script_file() == 0) {
+				reboot = 1;
+			}
 		}
+		// Check for the ORS file in /cache and attempt to run those commands.
+		if (OpenRecoveryScript::check_for_script_file()) {
+			gui_print("Processing OpenRecoveryScript file...\n");
+			if (OpenRecoveryScript::run_script_file() == 0) {
+				reboot = 1;
+			}
+		}
+		if (reboot) {
+			usleep(2000000); // Sleep for 2 seconds before rebooting
+			TWFunc::tw_reboot(rb_system);
+		} else {
+			DataManager::SetValue("tw_page_done", 1);
+		}
+		operation_end(1);
+		return 0;
 	}
 	return 0;
 }
@@ -1575,54 +1664,3 @@
 
 	return atol(key.c_str());
 }
-
-void* GUIAction::command_thread(void *cookie)
-{
-	string command;
-	FILE* fp;
-	char line[512];
-
-	DataManager::GetValue("tw_terminal_command_thread", command);
-	fp = popen(command.c_str(), "r");
-	if (fp == NULL) {
-		LOGERR("Error opening command to run.\n");
-	} else {
-		int fd = fileno(fp), has_data = 0, check = 0, keep_going = -1, bytes_read = 0;
-		struct timeval timeout;
-		fd_set fdset;
-
-		while(keep_going)
-		{
-			FD_ZERO(&fdset);
-			FD_SET(fd, &fdset);
-			timeout.tv_sec = 0;
-			timeout.tv_usec = 400000;
-			has_data = select(fd+1, &fdset, NULL, NULL, &timeout);
-			if (has_data == 0) {
-				// Timeout reached
-				DataManager::GetValue("tw_terminal_state", check);
-				if (check == 0) {
-					keep_going = 0;
-				}
-			} else if (has_data < 0) {
-				// End of execution
-				keep_going = 0;
-			} else {
-				// Try to read output
-				memset(line, 0, sizeof(line));
-				bytes_read = read(fd, line, sizeof(line));
-				if (bytes_read > 0)
-					gui_print("%s", line); // Display output
-				else
-					keep_going = 0; // Done executing
-			}
-		}
-		fclose(fp);
-	}
-	DataManager::SetValue("tw_operation_status", 0);
-	DataManager::SetValue("tw_operation_state", 1);
-	DataManager::SetValue("tw_terminal_state", 0);
-	DataManager::SetValue("tw_background_thread_running", 0);
-	DataManager::SetValue(TW_ACTION_BUSY, 0);
-	return NULL;
-}
diff --git a/gui/objects.hpp b/gui/objects.hpp
index 124c926..44c2b36 100644
--- a/gui/objects.hpp
+++ b/gui/objects.hpp
@@ -257,6 +257,8 @@
 // GUIAction - Used for standard actions
 class GUIAction : public GUIObject, public ActionObject
 {
+	friend class ActionThread;
+
 public:
 	GUIAction(xml_node<>* node);
 
@@ -281,20 +283,19 @@
 protected:
 	int getKeyByName(std::string key);
 	int doAction(Action action);
+	bool needsToRunInSeparateThread(const Action& action);
 	void simulate_progress_bar(void);
 	int flash_zip(std::string filename, std::string pageName, int* wipe_cache);
 	void reinject_after_flash();
 	void operation_start(const string operation_name);
 	void operation_end(const int operation_status);
-	static void* command_thread(void *cookie);
-	static void* sideload_thread_fn(void *cookie);
-	static void* openrecoveryscript_thread_fn(void *cookie);
 	time_t Start;
 
 	// map action name to function pointer
 	typedef int (GUIAction::*execFunction)(std::string);
 	typedef std::map<std::string, execFunction> mapFunc;
 	static mapFunc mf;
+	static std::set<std::string> setActionsRunningInCallerThread;
 
 	// GUI actions
 	int reboot(std::string arg);
@@ -323,7 +324,7 @@
 	int screenshot(std::string arg);
 	int setbrightness(std::string arg);
 
-	// threaded actions
+	// (originally) threaded actions
 	int fileexists(std::string arg);
 	int flash(std::string arg);
 	int wipe(std::string arg);
@@ -355,6 +356,26 @@
 	int simulate;
 };
 
+class ActionThread
+{
+public:
+	ActionThread();
+	~ActionThread();
+
+	void threadActions(GUIAction *act, size_t start_index);
+	void run(void *data);
+private:
+	struct ThreadData
+	{
+		GUIAction *act;
+		size_t start_index;
+	};
+
+	pthread_t m_thread;
+	bool m_thread_running;
+	pthread_mutex_t m_act_lock;
+};
+
 class GUIConsole : public GUIObject, public RenderObject, public ActionObject
 {
 public: