/*	server.c (IPC stuff) Ver 0.72

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, version 3 (or any later).  

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, see <http://www.gnu.org/licenses/>.		*/

#include "main.h"

const threadlock InitTL = {
	.cond = PTHREAD_COND_INITIALIZER,
	.lock = PTHREAD_MUTEX_INITIALIZER,
	.data = 666
};

GIOChannel *Gio = NULL;

int check_seesock () { 	/* called from main() */
	char buf[16];
	int remote, err = 0;

	if (Debug) g_print("check_seesocket()...\n");

	switch (file_check(Config->sock)) {
		case -2: return 0;      /* socket does not exist */
		case  0: 
			puts("Abnormal output from stat on seesocket");
			return -66; 
		case 49152: 
			break;	/* socket exists */ 
		default: 
			if (unlink(Config->sock)<0) return -11;
			else return 0;
	}

	if (Debug) g_print("Socket exists!\n");

// socket exists, so check if there is a running server on it
	if ((remote = loclcon(Config->sock))) {
		write(remote,"XOSEESOX\n",9);	// handshake, QV. serverLoop()
		getMsg(remote,buf,16);
		if (!strncmp(buf,"XOSEESOX",8)) {
			puts("See server running");
			close(remote);
			return -1;
		}
	} else err = errno;

/* stale socket file or other issue */
	if (remote) close(remote); 

	if (Debug > 1) {
		fprintf(stderr,"(no connect) err=%d\n",err);
		if (err) fprintf(stderr,"%s\n",strerror(err));
	}

	if ((unlink(Config->sock)) < 0) return -11;

	return 0;
}


int file_check (char *file) {
	int ftype;
	struct stat info;
	if (stat(file,&info)<0) {
		if (errno==2) return -2;
		else return 0;
	}
	ftype=(info.st_mode & S_IFMT);
	return ftype;   /* a regular file is 32768 -- a socket is 49152 */
}


int getMsg (int fd, char *buffer, int sz) {
	int err, c = 0, skip = 0;

	while ((err = read(fd,&buffer[c],1))) {
		if (err == -1) {
			perror("getMsg() read fail: ");
			skip++;
			if (skip == 5) return -1;
			continue;
		}
		if (buffer[c] == '\n' || c == sz) break;
		c++;
	}
	buffer[c] = '\0';

	return c;
}


int loclcon (char *sock) {
	struct sockaddr_un addr;
	size_t size;
	int con;

	con = socket(PF_LOCAL,SOCK_STREAM,0);
	addr.sun_family = AF_LOCAL;
	strcpy(addr.sun_path,sock);
	size = SUN_LEN(&addr);

	if (connect(con,(struct sockaddr*)&addr,size) == -1) return 0;

	return con;
}


int loclsckt (char *file) {
	struct sockaddr_un name;
	int sock;
	socklen_t size;

	if ((strlen(file)) > 107) return -2;
	if ((sock=socket(PF_LOCAL,SOCK_STREAM,0)) < 0) return -1;

	name.sun_family = AF_LOCAL;	/* the error for "file" exists is from bind (-3) */
	strcpy(name.sun_path, file);	
	size=SUN_LEN(&name);
	if ((bind(sock,(struct sockaddr*)&name,size) < 0)) {
		close(sock); 
		return -3;
	}
	return sock;
}


int send_remote (char *term) {	/* called from main() if server is running */
	char buffer[TRANS_MAX];
	struct sockaddr_un addr;
	size_t size;
	int remote, err;

	remote = socket(PF_LOCAL,SOCK_STREAM,0);
	addr.sun_family = AF_LOCAL;
	strcpy(addr.sun_path,Config->sock);
	size = SUN_LEN(&addr);

	if (Debug) fprintf(stderr,"send_remote()...\n");

	if ((err = connect(remote,(struct sockaddr*)&addr,size))) {
		fprintf(stderr, "send_remote() connect failed: %s\n",strerror(errno));
		exit (0);
	}

	if (Seefile->type == 'R') {
		sprintf(buffer,"LOAD %s",Seefile->path);
	} else {
		if (Seefile->path[0] == '/') sprintf(buffer, "SMAN *** %s",Seefile->path); /* section *** is out-of-tree */
		else if (Seefile->sec[0]) sprintf(buffer, "SMAN %s %s",Seefile->sec,Seefile->path);
		else sprintf(buffer,"MAN %s",Seefile->path);
	}
	if (term) { 
		strcat(buffer," FIND "); 
		strcat(buffer,term); 
	}
	strcat(buffer,"\n");

	write(remote,buffer,strlen(buffer));
	g_print("Request_sent...");

	if (Debug>0) g_print("\"%s\"...",buffer);

	getMsg(remote,buffer,TRANS_MAX);
	if (strncmp(buffer,"REQ OK",6) != 0) return -3;

	puts("done.");
	return 0;
}


void *serverLoop (void *p) {
/* called from tglServer() */
	threadlock *tlock = p;
	int mproc, remote, fd = tlock->data;
	char buffer[TRANS_MAX];
	struct sockaddr_un addr;
	socklen_t asz = sizeof(addr);

	if (Debug>0) fprintf(stderr,"serverLoop()...%d\n",fd);

	if (listen(fd,666) != 0) {
		perror("serverLoop() listen fail: ");
		return NULL;
	}

	pthread_mutex_lock(&tlock->lock);

	if ((mproc = accept(fd,(struct sockaddr*)&addr,&asz)) < 0) {
	// mproc is the socket watched by takecall()
		perror("serverLoop() accept fail #1: ");
		return NULL;
	}

	getMsg(mproc,buffer,TRANS_MAX);
	write(mproc,"SS LAUNCH\n",10);	// handshake, QV. tglServer()
	if (strcmp(buffer,"SS LAUNCH")) {
		fprintf(stderr, "serverLoop() handshake failed.\n");
		return NULL;
	}

	while (1) {		//////// NEXT: tglServer end this on toggle...
		if ((remote = accept(fd,(struct sockaddr*)&addr,&asz)) < 0) { 
			perror("startServer() accept fail #2: ");
			return NULL;
		}
		getMsg(remote,buffer,TRANS_MAX);
		if (!strcmp(buffer,"XOSEESOX")) {
			write(remote,"XOSEESOX\n",9); // handshake, QV. check_seesock()
			continue;
		} else if (!strcmp(buffer,"SS THEND")) {	// QV tglServer()
			if (Debug > 1) fprintf(stderr,"serverLoop() thread exiting\n");
			close(mproc);
			close(remote);
			pthread_exit(NULL);
		}
		write(remote,"REQ OK\n",7);	// QV. send_remote()
		close(remote);
		write(mproc,buffer,strlen(buffer));
		write(mproc,"\n",1);		/* all seetxt transmissions are newline terminated, qv. getMsg() */
	}
}


gboolean takecall(GIOChannel *gio, GIOCondition ignored) {
/* called by tglServer() via g_io_add_watch */
	int server = g_io_channel_unix_get_fd(gio);
	char buffer[TRANS_MAX], *tok, *sec;

	if (Debug>0) fprintf(stderr,"takecall()...server=%d\n",server);

	getMsg(server,buffer,TRANS_MAX);
//	g_io_channel_read_chars(gio,buffer,TRANS_MAX,NULL,NULL);

	if (Debug>0) g_print("recieved \"%s\"\n",buffer);

	if (!(tok=strtok(buffer," "))) return TRUE;
	if (!strcmp(tok,"LOAD")) {
		tok=strtok(NULL," \n");
		loadnew(tok,NULL,0);
	} else if (!strcmp(tok,"MAN")) {
		tok=strtok(NULL," \n");
		loadnew(tok,"\0",0);
	} else if (strcmp(tok,"SMAN") == 0) {
		sec=strtok(NULL," ");
		tok=strtok(NULL," \n");
		loadnew(tok,sec,0); 
	} else return TRUE;

	if ((tok=strtok(NULL," ")) && (strcmp(tok,"FIND")==0)) {
		tok=strtok(NULL," \n");
		if (!(tok)) return TRUE; 
		gtk_entry_set_text(GTK_ENTRY(Fent), tok);
		searchlight(); 
	}
	return TRUE;
}


void tglServer (GtkWidget *ignored, int server) {
/* called from main() or by button, handlequit(), and reconfigure() */
	int remote, tries = 0;
	static guint blink_TO, watch_ID; /* some Glib functions return "source" ID's */ 
	char messg[256];
	pthread_t tid;
	threadlock lock = InitTL;

	if (Debug>0) fprintf(stderr,"tglServer()...%d\n",server);

	if (server <= 0) {	/* button toggle sends 0, handlequit sends -1 */
		if (!Gio) {
			if (server == -1) return;
			if (!Config->sock[0]) return;
			if ((server = loclsckt(Config->sock)) < 3) {
				fprintf(stderr,"tglServer() could not start!\n");
				return;
			}
		} else {
		/* shut down server */
			if (Debug > 1) fprintf(stderr,"tglServer() off.\n");
			remote = loclcon(Config->sock);
			if (remote) {
				write(remote,"SS THEND\n",9);	// QV. serverLoop()
				close(remote);
			}
			else if (Debug) perror("Couldn't shut down server thread: ");
			g_io_channel_set_close_on_unref(Gio,TRUE);
			g_io_channel_shutdown(Gio,FALSE,NULL);
			g_io_channel_unref(Gio);
			Gio = NULL;
			g_source_remove(blink_TO);
			g_source_remove(watch_ID);	/* important!? */
			unlink(Config->sock);
			if (Sff.sw == 1) {
				gtk_container_remove(GTK_CONTAINER(ServTgl), ServOn);
				gtk_container_add(GTK_CONTAINER(ServTgl), ServOff);
				Sff.sw = 0;
			}
			return;
		}
	}

/* start server */
	lock.data = server;
	pthread_mutex_lock(&lock.lock);
	if (pthread_create(&tid,NULL,serverLoop,&lock)) {
		perror("tglServer() pthread_create fail: ");
		return;
	}
	pthread_detach(tid);
	pthread_mutex_unlock(&lock.lock);
	// establish connection to thread
	while (tries < 5) {
		if (!(remote = loclcon(Config->sock))) {
			if (Debug) perror("tglServer() connect fail: ");
		} else break;
		sleep(1);
		tries++;
		if (tries == 5) return;
	}

	write(remote,"SS LAUNCH\n",10);
	getMsg(remote,messg,256);
	if (strcmp(messg,"SS LAUNCH")) {
		fprintf(stderr, "tglServer() failed handshake >%s<.\n",messg); 	// QV. startServer()
		return;
	}
	Gio = g_io_channel_unix_new(remote);
	blink_TO = g_timeout_add((750),(GSourceFunc)blinktoggle,&Sff);
	watch_ID = g_io_add_watch(Gio,G_IO_IN,(GIOFunc)takecall,NULL);

	if (Debug>0) g_print("See server initialized...\n");
}
